简体   繁体   English

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

[英]c++ Polymorphism : segfault and reference strange behaviour with arrays

Here is a MWE that, when compiled with g++ -std=c++11 , produces a segmentation fault: 这是一个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;
}

The typical output is : 典型的输出是:

#############
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
#############

In the first part where a Base class is given to Other the references are coherent and the code runs properly. 在将Base类提供给Other的第一部分中,引用是一致的,并且代码可以正常运行。

But when a Child class is given to Other , the first reference is identical but the second is slightly different. 但是,当将Child类提供给Other ,第一个引用是相同的,但第二个引用则略有不同。 The direct consequence of that is that the b[1].rdn.get() is always (very close) '0'. 这样做的直接结果是b[1].rdn.get()始终(非常接近)为'0'。 The other indirect consequence is that the code ends with a SegFault... 另一个间接后果是代码以SegFault结尾。

Moreover, when Child::var is absent, the program works fine, all references are coherent and there is no SegFault. 此外,如果不存在Child::var ,则程序可以正常运行,所有引用均一致,并且没有SegFault。

What am I doing wrong ? 我究竟做错了什么 ? Is it impossible to create Other with a child of Base ? Base的子Base创建Other是不可能的吗? If so, it looks like the polymorphism is ruined... 如果是这样,看起来多态性就毁了……

EDIT 编辑

According to a nice answer : 根据一个不错的答案:

#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;
}

If you have an array of Child you can't treat it as an array of Base . 如果您有一个Child数组,则不能将其视为Base数组。 This means that when for example you call b[1].rnd.get() , you invoke undefined behavior. 这意味着例如当您调用b[1].rnd.get() ,您将调用未定义的行为。

Why is that? 这是为什么? When you have a Child* c and you convert it to Base* , the result of conversion is a pointer to a Base subobject of Child . 当您拥有Child* c并将其转换为Base* ,转换的结果是一个指向ChildBase子对象的指针。 So (Base*)c + 1 will point to the next byte after that subobject, which in your case is the first byte of var . 因此, (Base*)c + 1将指向该子对象之后的下一个字节,在您的情况下,它是var的第一个字节。

When Child doesn't have data members, (Base*)c + 1 points to the next object, so everything works. Child没有数据成员时, (Base*)c + 1指向下一个对象,因此一切正常。

In order to achieve polymorphic behavior you should create an array of pointers to Base : 为了实现多态行为,您应该创建一个指向 Base指针数组:

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

Initialize each array element with whatever descendant of Base and enjoy polymorphism. 使用Base任何后代初始化每个数组元素,并享受多态性。

Edit: as it was reasonably pointed out by vsoftco in the comments, it's better not to use raw pointers and arrays: 编辑:正如vsoftco在评论中合理指出的那样,最好不要使用原始指针和数组:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM