为什么使用虚拟基类会更改复制构造函数的行为 [英] Why does using a virtual base class change the behavior of the copy constructor

查看:52
本文介绍了为什么使用虚拟基类会更改复制构造函数的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的程序中,当B实际上是从A派生而C的实例(不是B)被复制时,不会复制 a 成员变量.

  #include< stdio.h>A类{上市:A(){a = 0;printf("A()\ n");}诠释};B类:虚拟公众A {};C级:公共B {上市:C() {}C(const C& from):B(from){}};template< typename T>空白测试() {T t1;t1.a = 3;printf("pre-copy \ n");T t2(t1);printf("post-copy \ n");printf("t1.a =%d \ n",t1.a);printf("t2.a =%d \ n",t2.a);}整型主要的() {printf("B:\ n");测试< B>();printf("\ n");printf("C:\ n");test C();} 

输出:

  B:一种()预复制复制后t1.a = 3t2.a = 3C:一种()预复制一种()复制后t1.a = 3t2.a = 0 

请注意,如果B通常是从A派生的(您删除了 virtual ),则会复制 a .

为什么在第一种情况下 a 不被复制( test< C>(),而B实际上是从A派生的?

解决方案

C ++ 11标准在12.6.2/10中说:

在非委托构造函数中,初始化在以下订单:
—首先,并且仅对于最派生类的构造器(1.8),虚拟基类按以下顺序初始化它们出现在定向的深度优先的从左到右的遍历中基类的非循环图,其中从左到右"是基类在派生类中的外观基本说明符列表.
— [直接基类等...]

基本上,这就是说-派生最多的类以定义它的方式负责初始化(在OP中:否,这导致默认初始化).标准中的后续示例具有与此处的OP类似的场景,只是ctor的参数为int.仅调用虚拟基础的默认ctor,因为在派生程度最高的类中未提供虚拟基础的显式"mem-initializer".

有趣的是,尽管也没有直接应用在这里,但也是12.6.2/7:

内存初始化程序[可能在 B()中的 A():A(){} .-pas],其中mem-initializer-id表示虚拟基数在执行以下任何类的构造函数期间,将忽略该类:不是最派生的类.

(我发现这很困难.该语言基本上说:我不在乎您编写的代码,我会忽略它."没有太多地方可以执行此操作,这似乎违反了.)不是最派生类的构造函数将是 B().这句话并不直接适用于此,因为 B 中没有显式构造函数,因此也没有mem-initializer.但是,尽管我找不到标准中的措辞,但必须假设(并且是一致的)相同的规则适用于生成的复制构造函数.

为了完整起见,Stroustrup在"C ++编程语言"(第4版,21.2.5.1)中说,关于某个派生类D最多且带有虚拟基V的类D:

没有明确提到V是D的基数这一事实是无关紧要的.虚拟基数的知识和初始化它的义务泡"到了派生最多的类上.虚拟基础始终被认为是其派生类最多的直接基础.

这正是Sam Varshavchik在先前的帖子中所说的.

然后,Stroustrup继续讨论,从D派生一个类DD有必要将V的初始化转移到DD,这可能会造成麻烦.这应该鼓励我们不要过度使用虚拟基类."

除非非常派生的类显式地执行某操作,否则我发现基类保持未初始化(非常准确地说是默认初始化)是相当晦涩和危险的.

派生最多的班级的作者必须深入研究他/她可能对继承层次没有兴趣或没有文档,也不能依赖例如他/他用来做正确的事情的图书馆(图书馆不能这样做).

我也不确定我是否同意其他文章中给出的基本原理(各个中间类中的哪个应该执行初始化?").该标准对初始化顺序有清晰的概念(深度优先从左到右遍历").难道不是强制要求遇到的第一个实际上是从基类继承的类执行初始化吗?

在12.8/15中规定了一个有趣的事实,即默认副本ctor 初始化虚拟库:

每个基本或非静态数据成员均以以下方式复制/移动适合其类型:
[...]
—否则,基数或成员是用x的相应基数或成员直接初始化.

虚拟基类子对象只能由隐式定义的复制/移动构造函数(请参见12.6.2).

无论如何,由于 C 是派生程度最高的类,因此复制 C (而不是 B )是责任-构造虚拟基础 A .

In the following program the a member variable is not copied when B is virtually derived from A and instances of C (not B) are copied.

#include <stdio.h>

class A {
public:
    A() { a = 0; printf("A()\n"); }

    int a;
};

class B : virtual public A {
};

class C : public B {
public:
    C() {}
    C(const C &from) : B(from) {}
};

template<typename T>
void
test() {
    T t1;
    t1.a = 3;
    printf("pre-copy\n");
    T t2(t1);
    printf("post-copy\n");
    printf("t1.a=%d\n", t1.a);
    printf("t2.a=%d\n", t2.a);
}

int
main() {
    printf("B:\n");
    test<B>();

    printf("\n");

    printf("C:\n");
    test<C>();
}

output:

B:
A()
pre-copy
post-copy
t1.a=3
t2.a=3

C:
A()
pre-copy
A()
post-copy
t1.a=3
t2.a=0

Note that if B is normally derived from A (you delete the virtual) then a is copied.

Why isn't a copied in the first case (test<C>() with B virtually derived from A?

解决方案

The C++11 standard says in 12.6.2/10:

In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where "left-to-right" is the order of appearance of the base classes in the derived class base-specifier-list.
— [direct base classes etc. ...]

This says it, basically -- the most derived class is responsible for initialization in whatever way it defines it (in the OP: it doesn't, which leads to default initialization). The subsequent example in the standard features a similar scenario as in the OP here, just with an int argument to the ctor; only the default ctor of the virtual base is called, because no explicit "mem-initializer" for the virtual base is provided in the most derived class.

Of interest, although nor directly applying here, is also 12.6.2/7:

A mem-initializer [the A() in a possible B(): A() {}. -pas] where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.

(I find that pretty tough. The language basically says "I don't care what you coded, I'm gonna ignore it." There are not so many places where it can do that, violating as-if.) That constructor of a not-most-derived-class would be B(). The sentence does not directly apply here because there is no explicit constructor in B, so there is no mem-initializer either. But although I could not find wording for that in the standard one must assume (and it is consistent) that the same rule applies for the generated copy constructor.

For completeness, Stroustrup says in "The C++ Programming Language" (4.ed, 21.2.5.1) about a most derived class D with a virtual base V down the road somewhere:

The fact that V wasn't explicitly mentioned as a base of D is irrelevant. Knowledge of a virtual base and the obligation to initialize it "bubbles up" to the most derived class. A virtual base is always considered a direct base of its most derived class.

That is exactly what Sam Varshavchik said in an earlier post.

Stroustrup then goes on to discuss that deriving a class DD from D makes it necessary to move V's intialization to DD, which "can be a nuisance. That ought to encourage us not to overuse virtual base classes."

I find it fairly obscure and dangerous that a base class stays uninitialized (well, more precisely: default-initialized) unless the most-derived class explicitly does something.

The author of the most-derived class must dive deep into an inheritance hierarchy s/he may have no interest in or documentation about and cannot rely on e.g. the library s/he uses to do the right thing (the library can't).

I'm also not sure I agree with the rationale given in other posts ("which of the various intermediate classes should perform the initialization?"). The standard has a clear notion of the initialization order ("depth-first left-to-right traversal"). Couldn't it mandate that the first class encountered which virtually inherits from a base performs the initialization?

The interesting fact that the default copy ctor does initialize the virtual base is prescribed in 12.8/15:

Each base or non-static data member is copied/moved in the manner appropriate to its type:
[...]
— otherwise, the base or member is direct-initialized with the corresponding base or member of x.

Virtual base class subobjects shall be initialized only once by the implicitly-defined copy/move constructor (see 12.6.2).

In any event, because C is the most derived class it is C's (and not B's) responsibility to copy-construct the virtual base A.

这篇关于为什么使用虚拟基类会更改复制构造函数的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆