C++指针协变

3
我从未想到C++具有指针协变性,因此会让你像这样自毁长城:
struct Base
{
    Base() : a(5) {}
    int a;     
};

struct Child1 : public Base
{
    Child1() : b(7) {}
    int b;
    int bar() { return b;}
};

struct Child2 : public Base
{
    Child2(): c(8) {}
    int c;
};

int main()
{
    Child1 children1[2];

    Base * b = children1;

    Child2 child2;

    b[1] = child2; // <------- now the first element of Child1 array was assigned a value of type Child2

    std::cout << children1[0].bar() <<  children1[1].bar(); // prints 57 
}

这是未定义行为吗?有没有办法防止它,或者至少从编译器得到警告?

1个回答

2

是的,这是未定义行为。

但是,在这一点上,典型的C++编译器不太可能能够识别出值得诊断的东西。但是,随着每年过去,C++编译器变得越来越聪明。谁知道多年后的情况会是什么样子...

然而,有一个小小的问题:

b[1] = child2; // <------- now the first element of Child1 array was assigned...

不是的。这不是第一个元素,而是第二个元素。第一个元素是b[0]。此外,b不是数组,它是一个指针。它是指向单个元素的指针,而不是指向两个元素数组的指针。

这就是未定义行为的原因所在。

它不是数组的原因是:

Base * b = children1;
children1会衰变成一个指向Child1 *类型的指针。如果事情就在这里结束了,那么你可以说b将是一个指向长度为2的数组的指针。
但事情并没有就此结束。衰变后的指针被转换为Base *类型的指针。你可以将一个指向子类的指针隐式转换为一个指向超类的指针。但(现在松散地讲)你不能将一个指向子类数组的指针转换为一个指向超类数组的指针。因此,b严格地来说是一个指向单个元素的指针,而b[1]则成为未定义行为。

我习惯将b [0]称为零元素。实际上,制作数组并不是必要的。我可以生成单个Child1 child1; Base * b =&child1; * b = child2; 并获得相同的未定义行为,对吗? - Amomum
不,这将是完全定义良好的行为。 - Sam Varshavchik
@Amomum:当然,仅仅因为它被定义得很好并不意味着它是一个好主意。也不意味着它做你认为它会做的事情。 - Nicol Bolas
@Amomum b[0] 是第零个元素,但也是第一个元素。而且,那个切片已经被定义:它只是执行了 operator=(Base&)。这不太可能有任何有用的作用,但确实是被定义了的。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接