List C.67 in the cpp core guideline says: A base class should suppress copying, and provide a virtual clone instead if "copying" is desired.
If the copy constructor is defined as deleted in the base, then the move operations are also suppressed for base class and all derived classes.
On the other hand, move operations may improve performance. My question is what would be realistic approach we should adopt when we design a class hierarchy?
Suppose we have the following class hierarchy? how should we design A and B to properly support copy and move operations.
class A{
public:
A(const std::string& as) = deleted;
//should we define other copy/move operators?
virtual void foo();//
virtual ~A();//
private:
std::string s;
};
class B: public A{
public:
//how do we define copy/move operators?
void foo() override;
~B() override;
private:
std::vector<std::string> vs;
};
Confusions
Copy vs. clone
First, note that clone is not “copy for objects of polymorphic type”: they are simply different operations with different semantics.
Copy (via a copy constructor) means “create an object of statically specified type with the value of another”. (Recall that constructors cannot be virtual, for want of a current object whose class could provide the behavior.) The user must specify the type, and should expect that the copied object is “reinterpreted” (sliced) as the known, specified type if it is in fact of a derived type.
clone copies an object of a dynamically known derived class as another object of that class. Since the argument determines the type of the result, the user cannot specify it and indeed does not (statically) know what is chosen. (Heap allocation is a corollary.)
Which one you want depends on what lifetime and type you want the result to have (including “the same type as that one” as a choice). I find it puzzling that someone would write a copy (e.g., a by-value parameter whose specified type is a concrete base class) and be surprised by what they had chosen.
Virtual assignment
Next, note that an abstract class need not fear slicing except on assignment (which must be via a reference). Assignment can be virtual (since an object already exists), but it can’t statically avoid slicing because the types need not match:
struct B {
virtual ~B()=default;
virtual B& operator=(const B&)=default;
// ...
};
struct D1 : B {
D& operator=(const B&) override;
// ...
};
struct D2 : B {/* ... */};
void f() {
B &&b=D1();
b=D2(); // ok
}
The assignment must use just the common B part, or… throw? If the assignment can fail, it’d be clearer to provide it as a function: perhaps bool assign_like(const B&) &; that returns false if the types differ.
Protection
So we indeed must do something about at least assignment if we want to avoid the risk of slicing. The Core Guidelines idea of deleting the assignment operator is reasonable, but I would just make it protected in each abstract base class.
Concrete leaves only
If you never inherit from a concrete class, that’s all you need to prevent slicing: the implicit (public) special member functions in the leaf classes do not slice, automatically use the base’s corresponding SMFs, and can in turn be used automatically as appropriate. (For example, the concrete classes may then be passed by value.)
“Deep” hierarchy
In a concrete base class, you have two choices for each SMF:
Make it protected anyway, denying the possibility of deliberately copying an object (even if the source’s complete object type is known statically).
Leave it available and send any confused users of the class hierarchy here to learn about the difference between copy and clone (and the impossibility of a “fully virtual” assignment).
Related
According to the C++ standard, a class that has virtual functions cannot have a trivial copy constructor:
A copy/move constructor for class X is trivial if it is not user-provided and if
— class X has no virtual functions (10.3) and no virtual base classes (10.1), and
— the constructor selected to copy/move each direct base class subobject is trivial, and
— for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;
otherwise the copy/move constructor is non-trivial.
Now, imagine a class hierarchy that satisfies all the mentioned conditions except the 'no virtual functions' condition:
struct I
{
virtual void f() = 0;
};
struct D final : I
{
void f() override
{
}
};
From an implementation's point of view those classes contain only a pointer to the virtual dispatch table. And the base class does not have any user-declared constructors, and the derived class is final, so the mentioned pointer can always have a constant value. Given all that, the copy constructor can be trivial, yet the standard explicitly forbids treating it as such.
On the other hand, the conditions of treating such classes as trivially destructible are met. The class does not declare a virtual destructor and uses implicitly-defined destructors.
Requiring triviality of destructors looks like mandating an optimization - implicitly defined destructors should not restore pointers to the virtual table in this case.
But such an optimization goes half-way in my opinion; classes with virtual functions still cannot be memcopied, even if they can be trivially destructed.
The questions:
Are there any reasons I did not think of from an implementation perspective why such classes should have non-trivial copy-constructors?
Are there any reasons the restrictions on triviality for copy-constructors cannot be relaxed in the standard?
Are there any reasons I did not think of from implementation perspective why such classes should have non-trivial copy-constructors?
There is quite obvious one: copy-constructor of I is not trivial. And it is not final, so there can be other derived classes. So it must be non-trivial and set virtual table pointer properly after memcpy, as there could be derived classes relying on it.
Are there any reasons the restrictions on triviality for copy-constructors cannot be relaxed in the standard?
1) Constructor triviality part was simply not revised with inclusion of final keyword.
2) People think that keywords like delete, final and overrride should help avoid most common errors, and clarify programmer intention, not change behavior of the program.
3) It complicates language:
A constructor is trivial, unless you have virtual function, then it is nontrivial, unless your class is final, then it is trivial again, unless something else, then it is not, unless...
4) Nobody though that it is worth writing formal paper for, proving usefulness of this addition to Committee and pushing this change into language.
The copies and moves of polymorphic classes cannot be trivial because it would break slicing copies and moves which copy a base type of an object's dynamic type. For example:
struct Base { virtual int f() { return 0; } };
struct D1 : Base { int f() override { return 1; } };
struct D2 : Base { int f() override { return 2; } };
int main() {
D1 d1;
D2 d2;
Base b = d1; // if this copy constructor were trivial, b would have D1's vtable
b.f(); // ...and this call would return 1 instead of 0.
b = d2; // Ditto: b would have D2's vtable
b.f(); // ...and this call would return 2 instead of 0.
}
Trivial constructors means that no additional effort is needed. So for copy c-tor/assigment operator it means simple memcpy. Virtual functions as you mentioned are creating vtable, which is table of pointers to functions. So if you'd try to copy memory of D object, you'll also copy its vtable. New object would have vtable with pointers pointing to old memory. This would crearly not an ideal situation.
Can we make a class copy constructor virtual in C++? How to use?
No you can't, constructors can't be virtual.
C++03 - 12.1 Constructors
4) A constructor shall not be virtual (10.3) or static (9.4). [...]
If you need something like this, you can look up the virtual constructor idiom here.
No you cannot.
Furthermore, the whole concept does not make sense. Virtual functions are functions that are dispatched based on the value of an object (the dynamic type of the object). When a constructor is called, the object does not yet have a value (because it has not yet been constructed). Therefore, no virtual dispatch can possibly occur.
Think about it. What semantics would such a constructor have?
No. C++ being static typed language, it is meaningless to the C++ compiler to create an object polymorphically. The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile time decision from C++ compiler perspective. If we make constructor virtual, compiler flags an error.
You cannot because the memory is allocated before the constructor is called based on the size of the new type not the copy operand. And if it did work it would be a special case that inverted polymorphism for a number of language constructs.
But that doesn't mean it can't be done with a little C++ magic. :)
There are couple cases where it is incredibly helpful, Serializing non-POD classes for instance. This example creates a virtual copy constructor that works using placement new.
Warning: This is an example that may help some users with specific problems. Do not do this in general purpose code. It will crash if the memory allocated for the new class is smaller than the derived class. The best (and only) safe way to use this is if you are managing your own class memory and using placement new.
class VirtualBase
{
public:
VirtualBase() {}
virtual ~VirtualBase() {}
VirtualBase(const VirtualBase& copy)
{
copy.VirtualPlacementCopyConstructor(this);
}
virtual void VirtualPlacementCopyConstructor(void*) const {}
};
class Derived :: public VirtualBase
{
public:
...
Derived(const Derived& copy) : ... don't call baseclass and make an infinite loop
{
}
protected:
void VirtualPlacementCopyConstructor(void* place) const
{
new (place) Derived(*this);
}
};
Never, it won't possible in C++.
Yes you can create virtual copy constructor but you can not create virtual constructor.
Reason:
Virtual Constructor:- Not Possible because c++ is static type language and create constructor as a virtual so compiler won't be able to decide what type of object it and leave the whole process for run time because of virtual keyword.
The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile time decision from C++ compiler perspective. If we make constructor virtual, compiler flags an error.
Virtual Copy constructor:- Yes Possible, consider clip board application. A clip board can hold different type of objects, and copy objects from existing objects, pastes them on application canvas. Again, what type of object to be copied is a runtime decision. Virtual copy constructor fills the gap here.
Assume, there is a hierarchy of two classes:
class Base {};
class Foo: public Base {};
Which are used like this:
Foo foo;
Base &base = foo;
Base moved = std::move(base);
In what state is foo after such move? Does the Standard impose any requirements on limitations on such code?
The standard does not make any such requirement on move constructors for C++ classes that are not a part of the standard library. The library writer is free to do what they want. For components that are a part of the standard library, the C++17 standard does say something (emphasis mine)
§ 20.5.5.15 Moved-from state of library types [lib.types.movedfrom]
Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
For classes in the standard library that are a part of an inheritance hierarchy, you would have to consult the documentation to see what happens when you try and move construct a base object from a base class rvalue reference a derived class object. So if you could specify which class in particular you were worried about I could try and help more.
Not sure how relevant this is to your question, but regarding what would really happen in such a constructor. Remember that the virtual function mechanism is disabled in constructors for the object that is going to be constructed, but not for the referenced object. In the following example
class Base {
public:
virtual vector<int> extract_vector() { ... }
// Move constructor can use virtual functions on the other object
Base(Base&& other) {
this->vec = other.extract_vector();
}
private:
std::vector<int> vec;
};
class Foo : public Base {
public:
vector<int> extract_vector() override { ... }
private:
// this does something ¯\\_(ツ)_/¯
SpecialVectorAdaptor<std::vector<int>> vec;
};
The virtual function extract_vector on other in the move constructor for Base will be called, and you can utilize virtual methods on the other object. But you cannot use any such virtual methods on the class that is going to be constructed.
So long story short, the behavior of the code you have shown will depend strictly on the implementation of the classes in question.
In your particular case, nothing happens, since both Base and Foo are empty classes.
For the more interesting situation that both Base and Foo have some movable members (or bases), since Base has no user-defined move constructor, the default move constructor will be used. This will move the Base part of foo to moved, but leave the rest intact. For example, after
struct Base { std::unique_ptr<int> X; };
struct Foo: Base { std::unique_ptr<int> Y; };
Foo foo;
foo.X.reset(new int);
foo.Y.reset(new int);
Base &base = foo;
Base moved = std::move(base);
foo.X is empty (nullptr) with the original value moved to moved, but still foo.Y still holds the initial value.
This demonstrates that when moving from a reference you potentially change the state of the referred to object, which can create all sorts of unwanted effects and is best avoided.
We use a framework that relies on memcpy in certain functions. To my understanding I can give everything that is trivially copyable into these functions.
Now we want to use a simple class hierarchy. We are not sure whether we can have a class hierarchy that results in trivially copyable types because of the safe destruction. The example code looks like this.
class Timestamp; //...
class Header
{
public:
uint8_t Version() const;
const Timestamp& StartTime();
// ... more simple setters and getters with error checking
private:
uint8_t m_Version;
Timestamp m_StartTime;
};
class CanData : public Header
{
public:
uint8_t Channel();
// ... more setters and getters with error checking
private:
uint8_t m_Channel;
};
The base class is used in several similar subclasses. Here I omitted all constructors and destructors. Thus the classes are trivially copyable. I suppose though that the user can write a code that results in a memory leak like this:
void f()
{
Header* h = new CanData();
delete h;
}
Is it right that the class hierarchy without the virtual destructor is a problem even if all classes use the compiler's default destructor? Is it therefore right that I cannot have a safe class hierarchy that is trivially copyable?
This code
Header* h = new CanData();
delete h;
will trigger undefined behavior since §5.3.5/p3 states:
In the first alternative (delete object), if the static type of the object to be deleted is different from its
dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the
static type shall have a virtual destructor or the behavior is undefined
and regardless of not having dynamically allocated objects contained in your derived class (really bad if you have), you shouldn't do it. Having a class hierarchy without the base class virtual destructor is not a problem per se, it becomes a problem when you try to mix static and dynamic types with delete.
Doing memcpy on a derived class object smells of bad design to me, I would rather address the need for a "virtual constructor" (i.e. a virtual clone() function in your base class) to duplicate your derived objects.
You can have your class hierarchy that is trivially copyable if you make sure that your object, its subobjects and base classes are trivially copyable. If you want to prevent users referring to your derived objects via base classes you could, as Mark first suggested, render the inheritance protected
class Header
{
public:
};
class CanData : protected Header
{ ^^^^^^^^^
public:
};
int main() {
Header *pt = new CanData(); // <- not allowed
delete pt;
}
Notice that you won't be able to use base pointers at all to refer to derived objects due to §4.10/p3 - pointer conversions.
If you delete a pointer to a derived type held as its base type and you don't have a virtual destructor, the derived types destructor won't be called, whether it's implicitly generated or not. And whether its implicitly generated or not, you want it to be called. If the derived type's destructor wouldn't actually do anything anyway though, it might not leak anything or cause a problem. If the derived type holds something like a std::string, std::vector, or anything with a dynamic allocation, you want the dtors to be called. As a matter of good practice, you always want a virtual destructor for base classes whether or not the derived classes destructors need to be called (since a base class shouldn't know about what derives from it, it shouldn't make an assumption like this).
If you copy a type like so:
Base* b1 = new Derived;
Base b2 = *b1;
You will only invoke Bases copy ctor. The parts of the object which are actually from Derived will not be involved. b2 will not secretly be a Derived, it will just be a Base.
My first instinct is "don't do that - find another way, a different framework, or fix the framework". But just for fun let's assume that for certain your class copy doesn't depend in any way on the copy constructor of the class or any of its comprised parts being called.
Then since you're clearly inheriting to implement rather than to substitute the solution is easy: Use protected inheritance and your problem is solved, because they can no longer polymorphically access or delete your object, preventing the undefined behavior.
It's almost safe. In particular, there is no memory leak in
Header* h = new CanData();
delete h;
delete h calls the destructor of Header and then frees the memory pointed to by h. The amount of memory freed is the same as was initially allocated at that memory address, not the sizeof(Header). Since Header and CanData are trivial, their destructors do nothing.
However, you must provide a virtual destructor to base even if it does nothing (by requirement of the standard to avoid undefined behaviour). A common guideline is that a destructor for a base class must be either public and virtual or protected and nonvirtual
Of course, you must beware slicing as usual.
Thanks all for posting various suggestions. I try a summarizing answer with an additional proposal for the solution.
The prerequisite of my question was to reach a class hierarchy that is trivially copyable. See http://en.cppreference.com/w/cpp/concept/TriviallyCopyable and especially the requirement of a trivial destructor (http://en.cppreference.com/w/cpp/language/destructor#Trivial_destructor). The class cannot need a destructor implemented. This restricts the allowed data members, but is fine for me. The example shows only C-compatible types without dynamic memory allocation.
Some pointed out that the problem of my code is undefined behaviour, not necessarily a memory leak. Marco quoted the standard regarding this. Thanks, really helpful.
From my understanding of the answers, possible solutions are the following. Please correct me if I am wrong. The solution's point is that the implementation of the base class must avoid that its destructor can be called.
Solution 1: The proposed solutions use protected inheritance.
class CanData : protected Header
{
...
};
It works but avoids that people can access the public interface of Header. This was the original intention to have a base class. CanData needs to forward these functions to Header. In the consequece, I would reconsider to use composition instead of inheritance here. But the solution should work.
Solution 2: Header's destructor must be protected, not the base class as a whole.
class Header
{
public:
uint8_t Version() const;
const Timestamp& StartTime();
// ... more simple setters and getters with error checking
protected:
~Header() = default;
private:
uint8_t m_Version;
Timestamp m_StartTime;
};
Then no user can delete Header. This is fine for me, because Header has no purpose on its own. With public derivation, the public interface remains available to the user.
My understanding is that CanData needs not implement a destructor to call the base class's desctructor. All can use the default destructor. I am not completely sure about this though.
All in all, the answers to my questions in the end of the origial positing are:
Is it right that the class hierarchy without the virtual destructor is a problem even if all classes use the compiler's default destructor?
It is only a problem if your destructor is public. You must avoid that people can access you desctrutor, except for derived classes. And you must ensure that derived classes call (implicitely) the base class's destructor.
Is it therefore right that I cannot have a safe class hierarchy that is trivially copyable?
You can make your base class safe with protected inheritance or a protected desctructor. Then you can have a hierarchy of trivially copyable classes.
Can we make a class copy constructor virtual in C++? How to use?
No you can't, constructors can't be virtual.
C++03 - 12.1 Constructors
4) A constructor shall not be virtual (10.3) or static (9.4). [...]
If you need something like this, you can look up the virtual constructor idiom here.
No you cannot.
Furthermore, the whole concept does not make sense. Virtual functions are functions that are dispatched based on the value of an object (the dynamic type of the object). When a constructor is called, the object does not yet have a value (because it has not yet been constructed). Therefore, no virtual dispatch can possibly occur.
Think about it. What semantics would such a constructor have?
No. C++ being static typed language, it is meaningless to the C++ compiler to create an object polymorphically. The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile time decision from C++ compiler perspective. If we make constructor virtual, compiler flags an error.
You cannot because the memory is allocated before the constructor is called based on the size of the new type not the copy operand. And if it did work it would be a special case that inverted polymorphism for a number of language constructs.
But that doesn't mean it can't be done with a little C++ magic. :)
There are couple cases where it is incredibly helpful, Serializing non-POD classes for instance. This example creates a virtual copy constructor that works using placement new.
Warning: This is an example that may help some users with specific problems. Do not do this in general purpose code. It will crash if the memory allocated for the new class is smaller than the derived class. The best (and only) safe way to use this is if you are managing your own class memory and using placement new.
class VirtualBase
{
public:
VirtualBase() {}
virtual ~VirtualBase() {}
VirtualBase(const VirtualBase& copy)
{
copy.VirtualPlacementCopyConstructor(this);
}
virtual void VirtualPlacementCopyConstructor(void*) const {}
};
class Derived :: public VirtualBase
{
public:
...
Derived(const Derived& copy) : ... don't call baseclass and make an infinite loop
{
}
protected:
void VirtualPlacementCopyConstructor(void* place) const
{
new (place) Derived(*this);
}
};
Never, it won't possible in C++.
Yes you can create virtual copy constructor but you can not create virtual constructor.
Reason:
Virtual Constructor:- Not Possible because c++ is static type language and create constructor as a virtual so compiler won't be able to decide what type of object it and leave the whole process for run time because of virtual keyword.
The compiler must be aware of the class type to create the object. In other words, what type of object to be created is a compile time decision from C++ compiler perspective. If we make constructor virtual, compiler flags an error.
Virtual Copy constructor:- Yes Possible, consider clip board application. A clip board can hold different type of objects, and copy objects from existing objects, pastes them on application canvas. Again, what type of object to be copied is a runtime decision. Virtual copy constructor fills the gap here.