c++多态性:segfault和引用数组的奇怪行为



这里有一个MWE,当使用g++ -std=c++11编译时,它会生成分段故障:

#include <iostream>
#include <random>
class Rand{
    public:
        Rand(double const& min_inclusive, double const& max_exclusive):
            mt_(std::random_device()()),
            dist_(min_inclusive,max_exclusive){}
        ~Rand(){}
        double get() { return dist_(mt_); }
    private:
        std::mt19937_64 mt_;
        std::uniform_real_distribution<double> dist_;
};
class Base {
    public:
        Base():rnd(0.0,1.0){ std::cout<<"Base created "<<&rnd<<" "<<rnd.get()<<std::endl; }
        virtual ~Base(){};
        Rand rnd;
};
class Child: public Base{
    public:
        Child():var(1.0){ std::cout<<"Child created"<<std::endl; }
        double var;
};
class Other {
    public:
        Other(Base* b):b(b){  std::cout<<"Other created "<<&(b[0].rnd)<<" and "<<&(b[1].rnd)<<"--->"<<b[0].rnd.get()<<" "<<b[1].rnd.get()<<std::endl; }
        Base* b;
};
int main(){
    unsigned int N(2);
    std::cout<<"#############"<<std::endl;
    Base* b(new Base[N]);
    Other o1(b);
    std::cout<<"#############"<<std::endl;
    Child* c(new Child[N]);
    Other o2(c);
    std::cout<<"#############"<<std::endl;
    delete[] b;
    delete[] c;
}

典型的输出是:

#############
Base created 0x239c020 0.226514
Base created 0x239ca00 0.902337
Other created 0x239c020 and 0x239ca00--->0.864321 0.302185
#############
Base created 0x239d3f0 0.573563
Child created
Base created 0x239ddd8 0.422187
Child created
Other created 0x239d3f0 and 0x239ddd0--->0.909183 4.94066e-324
#############

在第一部分中,Base类被赋予Other,引用如下连贯,代码运行正常。

但当Child类被赋予Other时,第一个引用是相同的但第二个略有不同。其直接后果是CCD_ 6总是(非常接近)"0"。其他间接后果代码以SegFault结尾。。。

此外,当Child::var不存在时,程序运行良好,所有引用是连贯的,不存在SegFault。

我做错了什么?是否不可能用的子项创建OtherBase?如果是这样的话,多态性似乎被破坏了。。。

编辑

根据一个不错的答案:

#include <iostream>
#include <random>
#include <vector>
#include <memory>
class Rand{
    public:
        Rand(double const& min_inclusive, double const& max_exclusive):
            mt_(std::random_device()()),
            dist_(min_inclusive,max_exclusive){}
        ~Rand(){}
        double get() { return dist_(mt_); }
    private:
        std::mt19937_64 mt_;
        std::uniform_real_distribution<double> dist_;
};
class Base {
    public:
        Base():rnd(0.0,1.0){ std::cout<<"Base created "<<&rnd<<" "<<rnd.get()<<std::endl; }
        virtual ~Base(){};
        Rand rnd;
};
class Child: public Base{
    public:
        Child():var(0.0){ std::cout<<"Child created"<<std::endl; }
        double var;
};
template<typename Type>
class Other {
    public:
        Other(unsigned int N):b_(N){
            std::cout<<"Other created "<<std::endl;
            for(unsigned int i(0);i<b_.size();i++){
                b_[i] = std::make_shared<Type>();
            }
        }
        std::vector<std::shared_ptr<Base> > b_;
};
int main(){
    unsigned int N(2);
    std::cout<<"#############"<<std::endl;
    Other<Base> o1(N);
    std::cout<<"#############"<<std::endl;
    Other<Child> o2(N);
    std::cout<<"#############"<<std::endl;
}

如果您有一个Child数组,则不能将其视为Base数组。这意味着,例如,当您调用b[1].rnd.get()时,您将调用未定义的行为。

为什么?当您有一个Child* c并将其转换为Base*时,转换的结果是指向ChildBase子对象的指针。因此,(Base*)c + 1将指向该子对象之后的下一个字节,在您的情况下,它是var的第一个字节。

Child没有数据成员时,(Base*)c + 1指向下一个对象,所以一切都正常。

为了实现多态行为,您应该创建一个指向Base:的指针数组

Base** b{new Base*[N]};

Base的任何后代初始化每个数组元素,并享受多态性。

编辑:正如微软在评论中合理指出的那样,最好不要使用原始指针和数组:

std::vector<std::shared_ptr<Base>> b {...}

最新更新