C++11 struct and class: Part 2 Resource
(JP 27/06/2017 originally presented during a live coding of the supporting code)
Writing a resource class:
- A class is by convention an object in which member variables hold a specific invariant
- A resource class will have (Rule of 3/5):
- hand-rolled destructor
- hand-rolled copy constructor and operators deleted or given proper semantic
- A resource class may have:
- hand-rolled constructors (or =default)
- move constructor and operators given proper semantic (make the class easier to use (like std::unique_ptr vs boost::scoped_ptr))
- C++11 allow for well-behaved class using generated default constructor:
- Either use well-behaved members
- Use built-in members with in-class initialization
- Semi Regular vs Regular
- By default these are non-regular type (cannot copy or compare)
- They can be made semi-regular types with default construction, and copy constructor & operator
- They can be made regular with a adding operator == and !=
Non Movable:
#include "catch.hpp"
#include <string>
namespace {
class ResourceTest1
{
public:
ResourceTest1() = default;
ResourceTest1( const std::string& strValue )
: m_strValue( new std::string( strValue ) )
{
}
ResourceTest1( const ResourceTest1& other ) = delete;
ResourceTest1& operator=( const ResourceTest1& other ) = delete;
const std::string* StrValue() const
{
return m_strValue;
}
~ResourceTest1()
{
delete m_strValue;
}
private:
std::string* m_strValue = nullptr;
};
} // namespace
TEST_CASE( "Use ResourceTest1 Resource-Non-Movable", "[resource-non-movable]" )
{
ResourceTest1 t1;
CHECK( t1.StrValue() == nullptr );
}
TEST_CASE( "Use ResourceTest non own Resource-Non-Movable", "[resource-non-movable]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
CHECK( *t1.StrValue() == val );
}
//TEST_CASE( "Use ResourceTest copy Resource-Non-Movable", "[resource-non-movable]" )
//{
// std::string val = "t1";
// ResourceTest1 t1{val};
// ResourceTest1 t2{ t1 };
//
// CHECK( *t1.StrValue() == val );
// // CHECK( t2 == t1 );
//}
//TEST_CASE( "Use ResourceTest move Resource-Non-Movable", "[resource-non-movable]" )
//{
// std::string val = "t1";
// ResourceTest1 t1{val};
// ResourceTest1 t2{ ResourceTest1{val} };
//
// CHECK( *t2.StrValue() == val );
// // CHECK( t2 == t1 );
//}
Movable:
#include "catch.hpp"
#include <string>
namespace {
class ResourceTest1
{
public:
ResourceTest1() = default;
ResourceTest1( const std::string& strValue )
: m_strValue( new std::string( strValue ) )
{
}
ResourceTest1( const ResourceTest1& other ) = delete;
ResourceTest1& operator=( const ResourceTest1& other ) = delete;
ResourceTest1( ResourceTest1&& other )
{
std::swap( m_strValue, other.m_strValue );
}
ResourceTest1& operator=( ResourceTest1&& other )
{
std::swap( m_strValue, other.m_strValue );
}
const std::string* StrValue() const
{
return m_strValue;
}
~ResourceTest1()
{
delete m_strValue;
}
private:
std::string* m_strValue = nullptr;
};
} // namespace
TEST_CASE( "Use ResourceTest1 Resource-Movable", "[resource-movable]" )
{
ResourceTest1 t1;
CHECK( t1.StrValue() == nullptr );
}
TEST_CASE( "Use ResourceTest non own Resource-Movable", "[resource-movable]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
CHECK( *t1.StrValue() == val );
}
//TEST_CASE( "Use ResourceTest copy Resource-Movable", "[resource-movable]" )
//{
// std::string val = "t1";
// ResourceTest1 t1{val};
// ResourceTest1 t2{ t1 };
//
// CHECK( *t1.StrValue() == val );
// // CHECK( t2 == t1 );
//}
TEST_CASE( "Use ResourceTest move Resource-Movable", "[resource-movable]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
ResourceTest1 t2{ ResourceTest1{val} };
CHECK( *t2.StrValue() == val );
// CHECK( t2 == t1 );
}
Semi-Regular:
#include "catch.hpp"
#include <string>
namespace {
class ResourceTest1
{
public:
ResourceTest1() = default;
ResourceTest1( const std::string& strValue )
: m_strValue( new std::string( strValue ) )
{
}
ResourceTest1( const ResourceTest1& other )
: m_strValue(
other.m_strValue ? new std::string( *other.m_strValue ) : nullptr )
{
}
ResourceTest1& operator=( const ResourceTest1& other )
{
ResourceTest1 tmp( other );
std::swap( m_strValue, tmp.m_strValue );
}
ResourceTest1( ResourceTest1&& other )
{
std::swap( m_strValue, other.m_strValue );
}
ResourceTest1& operator=( ResourceTest1&& other )
{
std::swap( m_strValue, other.m_strValue );
}
const std::string* StrValue() const
{
return m_strValue;
}
~ResourceTest1()
{
delete m_strValue;
}
private:
std::string* m_strValue = nullptr;
};
} // namespace
TEST_CASE( "Use ResourceTest1 Resource-Semi-Regular", "[resource-semi-regular]" )
{
ResourceTest1 t1;
CHECK( t1.StrValue() == nullptr );
}
TEST_CASE( "Use ResourceTest non own Resource-Semi-Regular", "[resource-semi-regular]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
CHECK( *t1.StrValue() == val );
}
TEST_CASE( "Use ResourceTest copy Resource-Semi-Regular", "[resource-semi-regular]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
ResourceTest1 t2{ t1 };
CHECK( *t1.StrValue() == val );
// CHECK( t2 == t1 );
}
TEST_CASE( "Use ResourceTest move Resource-Semi-Regular", "[resource-semi-regular]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
ResourceTest1 t2{ ResourceTest1{val} };
CHECK( *t2.StrValue() == val );
// CHECK( t2 == t1 );
}
Regular:
#include "catch.hpp"
#include <string>
namespace {
class ResourceTest1
{
public:
ResourceTest1() = default;
ResourceTest1( const std::string& strValue )
: m_strValue( new std::string( strValue ) )
{
}
ResourceTest1( const ResourceTest1& other )
: m_strValue(
other.m_strValue ? new std::string( *other.m_strValue ) : nullptr )
{
}
ResourceTest1& operator=( const ResourceTest1& other )
{
ResourceTest1 tmp( other );
std::swap( m_strValue, tmp.m_strValue );
}
ResourceTest1( ResourceTest1&& other )
{
std::swap( m_strValue, other.m_strValue );
}
ResourceTest1& operator=( ResourceTest1&& other )
{
std::swap( m_strValue, other.m_strValue );
}
friend bool operator==(const ResourceTest1& v1, const ResourceTest1& v2 )
{
return v1.m_strValue == v2.m_strValue ||
( v1.m_strValue && v2.m_strValue &&
*v1.m_strValue == *v2.m_strValue );
}
const std::string* StrValue() const
{
return m_strValue;
}
~ResourceTest1()
{
delete m_strValue;
}
private:
std::string* m_strValue = nullptr;
};
} // namespace
TEST_CASE( "Use ResourceTest1 Resource-Regular", "[resource-regular]" )
{
ResourceTest1 t1;
CHECK( t1.StrValue() == nullptr );
}
TEST_CASE( "Use ResourceTest non own Resource-Regular", "[resource-regular]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
CHECK( *t1.StrValue() == val );
}
TEST_CASE( "Use ResourceTest copy Resource-Regular", "[resource-regular]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
ResourceTest1 t2{ t1 };
CHECK( *t1.StrValue() == val );
CHECK( t2 == t1 );
}
TEST_CASE( "Use ResourceTest move Resource-Regular", "[resource-regular]" )
{
std::string val = "t1";
ResourceTest1 t1{val};
ResourceTest1 t2{ ResourceTest1{val} };
CHECK( *t2.StrValue() == val );
CHECK( t2 == t1 );
}
Presented at IndigoVision during a training session on 27/06/2017.
This work by Jean-Philippe DUFRAIGNE is licensed under a Creative Commons Attribution 4.0 International License.