C++: Blocking Constructors/Assignment Operators

Tower

Gawd
Joined
Oct 11, 2001
Messages
840
How many default Constructors and Assignment Operators are there to "block"?

Example:

Code:
class Obj
{
public:
	// Default constructor.
	Obj(void);

	// Destructor.
	~Obj(void);


private:

	// Copy constructors.
	Obj(Obj&);
	Obj(const Obj&);

	// Move constructors (new in C++11).
	Obj(Obj&&);
	Obj(const Obj&&);

	// Assignment operators.
	Obj operator= (Obj&);
	Obj operator= (const Obj&);
	Obj operator= (Obj&&);
	// Technically not necessary, as Move operations want to "reset" the
	// object passed in as an argument?
	Obj operator= (const Obj&&);
};

Am I missing any (and therefore is this correct?) Further, I realize that not all of these are necessary--for example, Obj(const Obj&) covers Obj(Obj&), but seeing as the compiler doesn't complain, it's easier for me to remember to block non-constant and constant for each type, and thus I recall all of these when I need to implement them.

Comments?
 
Keep in mind there's no clear-cut formula for all classes: Virtual functions (and virtual constructors, and destructors, etc.) complicate this, as well as interesting patterns like policy-based design, and sometimes even the publicness/protectedness/privateness of methods (even constructors and destructors) must change to accomodate design and intent.

Also, simple classes will not require explicit copy constructors, move constructors, assignment operators, or destructors: You only need them if you explicitly allocate and free resources within the code of your class (such as memory) and need to make deep copies or otherwise manage those resources upon copy/move/assignment/destruction invocations. Otherwise, the compiler will fill in those missing functions by default with correct shallow copies, etc. Note that you can still rely on compiler-supplied defaults even if your class contains member objects which themselves allocate memory, such as STL containers, because the actual memory management is handled from within those containers, and the compiler-supplied defaults will correctly call the copy/move/assignment/destruction methods for each of your class's members.

In fact, when your class does not manually manage resources, it is not only possible but advisable to utilize the default compiler-generated destructor and copy/move assignment/constructor methods. Doing so helps avoid so much explicit boilerplate code, which makes your class more compact and readable. It also helps you avoid introducing unnecessary bugs when your manually coded copy/move assignment and constructor methods get out of sync with your member variables as you continue to code the class.

Other than that, your basic template looks mostly correct to me, with three exceptions:
  • Unless the move semantics of C++11 have forced a change in coding conventions, your assignment operators should probably be returning an Obj& (through return *this) rather than an Obj.
  • I know you already addressed this, but I'd seriously advise against the versions of your copy constructor and copy assignment that take a non-const Obj&. Except for move construction and move assignment, your copy constructors and assignment operators should always take a const Obj& or an Obj passed by value, because assignment should not modify the source object, and you'll want the compiler to complain if you accidentally violate that. Since including a const version is necessary just to pass in const objects, sticking only with that version also helps reduce code duplication. I suppose there might be some rare corner case where you absolutely have to trash your input arguments, but that would be a special case rather than something suitable for a generic template...and in such a case, you probably won't have copy constructors or copy assignment methods that take a const Obj& anyway.
  • I'm still just learing about C++11, but const rvalue references are a bit bizarre (http://codesynthesis.com/~boris/blog/2012/07/24/const-rvalue-references/). I can't imagine they'd be worth maintaining two versions of your move assignment and move construction methods, and my gut (but not an intimate understanding of the standard) says the standard might even prohibit attempting moves on them in the first place, so I'd definitely stick with using non-const Obj&&'s for your move assignment operator and move constructor.

All told, the "template" (not as in C++ templates, but as in blueprint/skeleton) you're going for should probably look more like this:
Code:
class Obj
{
public:
	// Default constructor.
	Obj(void);

	// Destructor.
	~Obj(void);

private:
	// Copy constructors.
	Obj(const Obj&);

	// Move constructors (new in C++11).
	Obj(Obj&&);

	// Assignment operators.
	Obj& operator= (const Obj&);
	// Move assignment
	Obj& operator= (Obj&&);
};

When you have a simple class that doesn't deal with any kind of manual resource management, you should instead be using something more like the following minimal skeleton:
Code:
class Obj
{
public:
	// Default constructor.
	Obj(void);
	//  Or some other constructor initializing the class with other values
	Obj(int cake);

private:
	//  Err, that's it, other than the specific functionality you need to add.
};
 
Last edited:
Very interesting. I quite like the idea of prohibiting implicit conversions with delete like that. Thanks, Professor.
 
Back
Top