2
votes

According to standard:

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.

I think the standard introduced the concept "trival cp/mv ctor" to infer that you can just copy the classes using std::memcpy instead of calling constructor, and there'll be no undefined behavior.

However, the standard doesn't allow the existance of virtual functions, of which I think is against the spirit of "trival cp/mv ctor". A classes with a vtable pointing to virtual functions can still be copied with std::memcpy and has the right behavior. After all, you can't change the vtable of a class in runtime -- that will break other instances of this class.

So, why can't classes with no user-provide cp/mv ctor and have virtual functions but no virtual bases have "trival cp/mv ctor"?

2
What happens when you copy just the base sub-object out of a derived object? With memcpy I mean.StoryTeller - Unslander Monica
But cp/mv ctor is referred to copying or moving the from and to the same type. The whole concept of "trival" is built on the bases of one same class, derived classes or base classes have nothing to do with this.JiaHao Xu
Consider struct B { virtual ~B() {} }; struct D : B {}; D x; B y, *p; p = &x; memcpy(&y, p, sizeof y);.melpomene
@JiaHaoXu You can have a base class pointer point to a derived class. In this case you will copy a wrong vtable pointer.llllllllll

2 Answers

3
votes

It's not a trivial type because making sure the "vptr" points at the correct "vtable" is not as trivial as copying the value of a pointer. We can copy out just the base sub-object. We don't need to always deal with the most derived object type.

void bar(base const& b) { 
  b.overriden_function();
}

void foo(base const& b) {
  auto other_b = b;
  bar(other_b);
}

int main() {
  derived d;
  foo(d);
}

Let's assume base is trivial. So the copy is done as you say. The vptr of b is pointing at the vtable of derived. So now we obtained an object whose vtpr points at the wrong vtable. And we call an overriden function.

Boom!

1
votes

Consider a simple example:

#include <iostream>

struct ConcreteBase {
    virtual void method() {
        std::cout << "Basic implementation" << std::endl;
    }
};
struct Derived: ConcreteBase {
    Derived(int x): extraState{x} {}
    void method() {
        std::cout << "Overridden (" << extraState << ')' << std::endl;
    }
private:
    const int extraState;
};

int main() {
    ConcreteBase const &b = Derived(42);
    ConcreteBase b1{b};
    b1.method();
}

How exactly would you use memcpy to copy the vtable here? (More broadly, how would you trivialize ConcreteBase's copy ctor?)