9

如上面的标题所示,我的问题只是 C++ 转换是否确实创建了目标类的新对象。当然,在提出这个问题之前,我已经使用了 Google、MSDN、IBM 和 stackoverflow 的搜索工具,但我找不到合适的答案来回答我的问题。

让我们考虑以下使用虚拟继承解决的菱形问题的实现:

#include <iostream>
#include <cstdlib>

struct A
{
  int a;

  A(): a(2) {  }
};

struct B: virtual public A
{
  int b;

  B(): b(7) {  }
};

struct C: virtual public A
{
  int c;

  C(): c(1) {  }
};

struct END: virtual public B, virtual public C
{
  int end;

  END(): end(8) {  }
};

int main()
{
  END *end = new END();
  A *a = dynamic_cast<A*>(end);
  B *b = dynamic_cast<B*>(end);
  C *c = dynamic_cast<C*>(end);

  std::cout << "Values of a:\na->a: " << a->a << "\n\n";
  std::cout << "Values of b:\nb->a: " << b->a << "\nb->b: " << b->b << "\n\n";
  std::cout << "Values of c:\nc->a: " << c->a << "\nc->c: " << c->c << "\n\n";

  std::cout << "Handle of end: " << end << "\n";
  std::cout << "Handle of a: " << a << "\n";
  std::cout << "Handle of b: " << b << "\n";
  std::cout << "Handle of c: " << c << "\n\n";
  system("PAUSE");
  return 0;
}

据我了解,B 和 C 的实际结构通常由 A 的嵌入式实例和 B 的变量组成。C, 被销毁,因为 B 和 C 的虚拟 A 被合并到 END 中的一个嵌入对象以避免歧义。由于(我一直认为)dynamic_cast 通常只会将指针存储的地址增加嵌入式(cast)目标类的偏移量,因此由于目标(B 或 C)类被分为几个部分。

但是,如果我使用 MSVC++ 2011 Express 运行示例,一切都会按预期发生(即它将运行,所有 *.a 输出 2),指针仅略有不同。因此,我怀疑强制转换只是将源指针的地址移动了 B/C 实例的内部偏移量。

但是怎么做?B / C的结果实例如何知道共享A对象的位置。由于 END 对象中只有一个 A 对象,而 B 和 C 中通常有一个 A 对象,因此 B 或 C 都不能有 A 的实例,但实际上,两者似乎都有它的实例。

或者virtual仅将对 A 成员的调用委托给中央 A 对象,而不删除从 A 继承 virtual 的每个基类的相应 A 对象(即virtual实际上不会破坏继承对象和嵌入对象的内部结构,而只是不使用它们的虚拟化( = 共享)成员)?

还是virtual创建一个新的“偏移映射”(即告诉所有成员相对于指向类实例的指针的地址偏移的映射,我不知道实际术语)以便此类铸造对象处理它们的“分布性”?

我希望我已经澄清了一切,在此先感谢
BlueBlobb

PS:
如果有语法错误,我很抱歉,我只是一个热爱啤酒的巴伐利亚人,不是母语人士:P

编辑:
如果添加了这些行来输出所有 int a 的地址:

  std::cout << "Handle of end.a: " << &end->a << "\n";
  std::cout << "Handle of a.a: " << &a->a << "\n";
  std::cout << "Handle of a.b: " << &b->a << "\n";
  std::cout << "Handle of a.c: " << &c->a << "\n\n";

它们相同意味着确实只有一个 A 对象。

4

4 回答 4

6

我的问题只是 C++ 转换是否确实创建了目标类的新对象。

是的,转换为类类型会创建该类型的新临时对象。

请注意,您的示例不会在任何地方转换为类:它执行的唯一转换是指针类型。这些强制转换确实创建了新的指针实例——但不是指向的对象。我不确定您的示例应该展示什么,也不知道它与您提出的问题有何关系。

此外,dynamic_cast在您使用它的地方是不必要的;隐式转换也可以。

因为(正如我一直认为的那样)dynamic_cast 通常只会将指针存储的地址增加嵌入式(转换的)目标类的偏移量

你一定在想static_cast什么。dynamic_cast功能更强大。例如,即使它们在编译时不相关,它也可以通过向下到B*然后备份另一个分支来强制转换。利用运行时类型信息。C*END*dynamic_cast

B / C的结果实例如何知道共享A对象的位置。

这取决于实现。一个典型的实现会在派生类实例中保留空间来存储到它的虚拟基类实例的偏移量。最派生类的构造函数初始化所有这些偏移量。

于 2013-09-07T03:13:02.973 回答
5

不,您只是看到了多重继承的影响。为了将指针转换为不同的基本类型,必须将其调整为表示该确切类型的对象部分。编译器知道指针的原始类型和结果类型,因此它可以应用必要的偏移量。为了使派生类型满足“is-a”要求,它必须具有内置的必要结构来模拟所有基本类型。

在一种情况下,强制转换可以创建新对象,那就是当您强制转换为指针或引用类型以外的类型时。除非您为该类型定义了强制转换运算符,否则这通常是不可能的。

于 2013-09-07T03:13:23.643 回答
0

您给出的示例使用指针。

A* a = dynamic_cast<A*>(end);

所以这里创建的唯一“新”事物是另一个指针,它将指向“end”指向的对象的“A”vtable。它实际上并没有构造您正在使用的类/结构类型的新对象。

对比

A a;
B b(a);

这里创建了一个新对象。但除此之外,转换不会创建目标转换类型的新对象。

指针不同的原因是因为它们指向不同的 vtable,这些 vtable 位于底层对象的数据部分之前。

例子:

#include <iostream>

using namespace std;

struct A {
    int a[64];
    A() { cout << "A()" << endl; }
    A(const A&) { cout << "A(A&)" << endl; }
    A& operator = (const A&) { cout << "A=A" << endl; return *this; }
};

struct B : virtual public A {
    int b[64];
    B() { cout << "B()" << endl; }
    B(const B&) { cout << "B(B&)" << endl; }
    B(const A&) { cout << "B(A&)" << endl; }
    B& operator = (const B&) { cout << "B=B" << endl; return *this; }
    B& operator = (const A&) { cout << "B=A" << endl; return *this; }
};

struct C : virtual public A {
    int c[64];
    C() { cout << "C()" << endl; }
    C(const C&) { cout << "C(C&)" << endl; }
    C(const B&) { cout << "C(B&)" << endl; }
    C(const A&) { cout << "C(A&)" << endl; }
    C& operator = (const C&) { cout << "C=C" << endl; return *this; }
    C& operator = (const B&) { cout << "C=B" << endl; return *this; }
    C& operator = (const A&) { cout << "C=A" << endl; return *this; }
};

struct END : virtual public B, C {
    int end[64];
    END() { cout << "END()" << endl; }
    END(const END&) { cout << "END(END&)" << endl; }
    END(const C&) { cout << "END(C&)" << endl; }
    END(const B&) { cout << "END(B&)" << endl; }
    END(const A&) { cout << "END(A&)" << endl; }
    END& operator = (const END&) { cout << "END=END" << endl; return *this; }
    END& operator = (const C&) { cout << "END=C" << endl; return *this; }
    END& operator = (const B&) { cout << "END=B" << endl; return *this; }
    END& operator = (const A&) { cout << "END=A" << endl; return *this; }
};

int main() {
    END* end = new END();

    A *a = dynamic_cast<A*>(end);
    B *b = dynamic_cast<B*>(end);
    C *c = dynamic_cast<C*>(end);

    std::cout << "end = " << (void*)end << std::endl;
    std::cout << "a = " << (void*)a << std::endl;
    std::cout << "b = " << (void*)b << std::endl;
    std::cout << "c = " << (void*)c << std::endl;

    // the direct pointers are going to have to differ
    // to point to the correct vtable. what about 'a' in all cases?
    std::cout << "end->a = " << (void*)&(end->a) << std::endl;
    std::cout << "a->a = " << (void*)&(a->a) << std::endl;
    std::cout << "b->a = " << (void*)&(b->a) << std::endl;
    std::cout << "c->a = " << (void*)&(c->a) << std::endl;


}

你可以在这里看到运行:http: //ideone.com/0QAoWE

于 2013-09-07T04:00:22.240 回答
0

至少对于 VS 2017 中的 MSVC,答案是肯定的。

// Value is a struct that contains a member: std::string _string;
// _value is a std::variant<> containing a Value as one member

template <> std::string const &Get<std::string>() const
{
    // Required pre-condition: _value.index() == TYPE_VALUE
    Value const &value = std::get<TYPE_VALUE>(_value);
    return static_cast<std::string>(value._string);
}

std::string const &test()
{
    static std::string x = "hello world";
    return static_cast<std::string>(x);
}

Get()是来自一个更大项目的一个非常小的片段,如果没有其他数百行代码的支持,它将无法运行。 test()是我迅速拼凑起来调查的东西。

如所写,Get()生成以下警告:

警告 C4172:返回局部变量或临时地址

虽然test()编译干净。如果我删除static_cast<>from Get(),它也可以干净地编译。

PS事后看来,我应该重命名_value为类似的东西_payload,因为它可以包含比Value.

于 2018-08-12T08:39:13.597 回答