将派生类的指针数组转换为指向基类的指针数组

时间:2014-07-16 16:17:17

标签: c++ arrays pointers

以下是一些说明问题的代码:

#include <iostream>

class Base {
};

class Derived : public Base {
};

void doThings(Base* bases[], int length)
{
    for (int i = 0; i < length; ++i)
        std::cout << "Do ALL the things\n";
}

int main(int argc, const char * argv[])
{
    Derived* arrayOfDerived[2] = { new Derived(), new Derived() };
    doThings(arrayOfDerived, 2); // Candidate function not viable: no known conversion from 'Derived *[2]' to 'Base **' for 1st argument

    // Attempts to work out the correct cast
    Derived** deriveds = &arrayOfDerived[0];
    Base** bases = dynamic_cast<Base**>(deriveds); // 'Base *' is not a class

    Base** bases = dynamic_cast<Base**>(arrayOfDerived); // 'Base *' is not a class

    // Just pretend that it should work
    doThings(reinterpret_cast<Base**>(arrayOfDerived), 2);

    return 0;
}

Clang产生评论中给出的错误。问题是:&#34;是否有正确的方法将arrayOfDerived投射到doThings可以采取的行为?

奖金标记:

  1. 为什么clang会产生错误&#34; 'Base *' is not a class&#34;在给定的行?我知道Base*不是一个类,它是一个指针。试图告诉我的错误是什么?为什么设计dynamic_cast以便在dynamic_cast<T*>T必须是一个类?
  2. 使用reinterpret_cast迫使一切工作有什么危险?
  3. 一如既往地谢谢:)

3 个答案:

答案 0 :(得分:2)

没有。你在这里要求的是协变数组类型,它不是C ++的一个特性。

使用reinterpret_cast或C风格转换的风险是,虽然这适用于简单类型,但如果使用多个或虚拟继承,它将失败,并且可能在虚拟存在时中断功能(取决于实现)。为什么?因为在这些情况下,static_castdynamic_cast 实际上可能会更改指针值。也就是说,给定

class A {
  int a;
};

class B {
  string s;
};

class C : public A, B {
  double f[4];
};

C *ptr = new C();

除非课程为空,否则ptr == (A *)ptr和/或ptr == (B *)ptr将为false。只需要一点时间就可以解决为什么必须如此;如果你使用单继承并且没有vtable,那么可以保证子类的布局与超类的布局相同,后面跟着子类中定义的成员变量。但是,如果您有多个继承,则编译器必须选择以某种顺序或其他顺序放置类;我不确定C ++标准对它的评价(你可以检查 - 它可能以某种方式定义或约束布局顺序),但无论它选择什么顺序,都应该是显而易见的其中一个强制转换必须导致更改指针值。

答案 1 :(得分:2)

代码中的第一个错误是您使用的语法应该避免:

void doThings(Base* bases[], int length)

如果您查看错误消息,它会告诉您bases实际上是Base**,如果您真的想要传递这样的指针,那也应该写下来。

第二个问题与类型安全有关。想象一下这段代码,我把指针数组减少到一个指针:

class Base {...};
class Derived1: public Base {...};
class Derived2: public Base {...};
Derived1* p1 = 0;
Base*& pb = p1; // reference to a pointer
pb = new Derived2(); // implicit conversion Derived -> Base

如果编译了这段代码,p1现在会突然指向Derived2!将指针派生到指针到基础的简单转换的重要区别在于我们在这里有一个引用,并且你的情况下的指针也没有区别。

您可以使用两种有效的变体:

Base* pb = p1; // pointer value conversion
Base* const& pb = p1; // reference-to-const pointer

应用于您的代码,这涉及将指向数组的数组复制到指向基类的指针数组,这可能是您想要避免的。不幸的是,C ++类型系统没有提供任何不同的方法来直接实现您想要的。我不确定原因,但我认为至少根据标准指针可以有不同的大小。

我会考虑两件事:

  1. 将doThings()转换为带有两个迭代器的函数模板。这遵循STL的精神,允许使用不同的指针类型或其他提供迭代器接口的调用。
  2. 只需写一个循环。如果循环的主体很大,你也可以考虑将其作为函数提取。

答案 2 :(得分:2)

在回答你的主要问题时,答案并非如此, 因为没有办法合理地实施它。有&#39; S 无法保证Base*中的实际地址 与Derived*中的那些相同,事实上,有很多 他们不是的情况。在多重继承的情况下, 这两个基地的地址都不一样 派生,因为它们必须具有不同的地址 其他。如果你有一个Derived*的数组,不管它是不是 std::vector<Derived*>Derived**指向第一个 数组的元素,获取Base*数组的唯一方法是 通过复制:使用std::vector<Derived*>,这看起来 类似的东西:

std::vector<Base*> vectBase( vectDerived.size() );
std::transform(
    vectDerived.cbegin(),
    vectDerived.cend(),
    vectBase.begin(),
    []( Derived* ptr ) { return static_cast<Base*>( ptr ); } );

(您可以使用Derived**完成相同的操作 Base**,但您必须使用已知长度进行调整。)

至于你的&#34;奖金&#34;问题:

  1. Clang(我怀疑所有其他编译器都存在), 产生错误Base*不是一个类,因为它不是 一类。您在dynamic_cast之间尝试Base**Derived**,指向的类型是Base*Derived*dynamic_cast需要指向的类型 成为班级。

  2. reinterpret_cast唯一的危险就是它不会受到影响 工作。您将获得未定义的行为,这可能会发生 在一些简单的案例中工作,但一般都不会工作。你&#39; 11 最终得到编译器认为是Base*的东西, 但它没有实际指向Base*