Получение чистой ошибки виртуальной функции при попытке вытолкнуть unique_ptrs в вектор - PullRequest
0 голосов
/ 28 октября 2018

Итак, у меня есть абстрактный класс, названный MyClassParent, от которого наследуется MyClass.Я запускаю следующий код:

        for(auto e:elements){
            MyClass m = *this;
            MyClass * mpointer = &m;
            if(mpointer->xLargerthanY(x,y)){
                    rv.push_back(unique_ptr<MyClassParent>(mpointer));
                    if(!rv[0]->solved()) cout<<"true";//works as expected
            }
        }
        rv[0]->solved();//gives pure virtual function called error

Что странно, это то, что rv [0] -> solve () внутри для каждого цикла работает, как и ожидалось, возвращает true, если объект имеет x больше, чем y.Но если я вызываю функцию извне для каждого цикла, я получаю чисто виртуальную функцию с именем error, которая никогда не должна происходить, так как я переопределяю solve () в дочернем классе.Я подозреваю, что это как-то связано с функцией unique_ptr, так как мой решенный метод не вносит изменений в объект и возвращает только true из false.

Я проверил это со многими другими методами, все они работают внутри forкаждый цикл, но как только я выхожу из него, я получаю чисто виртуальную функцию с именем error.

Ответы [ 3 ]

0 голосов
/ 28 октября 2018

Хорошо, давайте начнем с небольшого вступления, потому что вы, кажется, не разбираетесь в некоторых вещах, необходимых для понимания умных указателей:

Длительность автоматического хранения: время жизни объектауправляется компилятором.Его время жизни определяется областью действия связанной переменной.

Например:

{
    X x; // lifetime of x starts here
    // ....

} // lifetime of x ends here

Продолжительность динамического хранения: Время жизни объекта управляется программистом.Он начинается с вызова new и заканчивается вызовом delete (это немного упрощается).

Например:

auto foo(X* x)
{
    delete x; // lifetime ends with delete
}


{
    X* x = new X{}; // lifetime starts with new

    foo(x);
}

В C ++ вам никогда не следует явно вызыватьnew / delete и используйте вместо этого умные указатели.

unique_ptr (если не указано иное) при уничтожении автоматически вызовет delete на указателе, который он удерживает.Это причина, по которой он должен быть снабжен указателем на динамический объект, т.е. выделен с new.Это одна из ваших проблем.

X x;

std::unique_ptr<X> p{&x};
// p receives a pointer to an automatic storage duration
// this is 100% wrong. The destructor for x would be called twice
// once as part of the automatic lifetime of x
// and then as part of the destructor of p
// resulting in UB

это то, что вы делаете здесь:

MyClass m = ...;
MyClass * mpointer = &m;
unique_ptr<MyClassParent>(mpointer);
// unique_ptr receives a pointer to an automatic storage duration object

Как будто этого было недостаточно, другая проблема, с которой вы сталкиваетесь, - это доступ к свисающий указатель .

Область действия m находится в пределах for.Вектор содержит указатели на такие объекты, и эти объекты выходят из области видимости после каждой итерации.Когда вы делаете rv[0], вы получаете доступ к объекту, срок жизни которого истек.Опять неопределенное поведение.

Надеюсь, вы лучше понимаете, что делает unique_ptr и какую проблему он решает.Решение - как показал Сторри Теллер - использовать make_unique.

Что делает make_unique: он вызывает new и создает unique_ptr из этого указателя, возвращенного new.Вы можете сделать это вручную, но не должны из-за других проблем: Различия между std :: make_unique и std :: unique_ptr

0 голосов
/ 28 октября 2018

Как указывает @StoryTeller, это неопределенное поведение, но позвольте мне объяснить, почему оно ведет себя так, как в этом случае.Поскольку это неопределенное поведение, нет никакой гарантии, что оно будет вести себя так в разных компиляторах или системах, но я объясню, почему есть хорошие шансы:

    for(auto e:elements){
        MyClass m = *this;
        MyClass * mpointer = &m;
        if(mpointer->xLargerthanY(x,y)){
                rv.push_back(unique_ptr<MyClassParent>(mpointer));
                if(!rv[0]->solved()) cout<<"true";//works as expected
        }
    }
    rv[0]->solved();//gives pure virtual function called error

Здесь

    for(auto e:elements){
        MyClass m = *this;
        ....
    }

Указатель на m сохраняется в векторе rv.Но когда m существует область действия, объект уничтожается.Код неявно вызывает MyClass::~MyClass(), который в итоге заменяет виртуальную таблицу объекта.Сначала производный класс уничтожается, и на последнем этапе этого уничтожения виртуальная таблица заменяется так, чтобы у объекта no была виртуальная таблица базы.В базе solved() является чисто виртуальным, поэтому вызов:

rv[0]->solved();

, поэтому вызов этой функции позволяет найти только определение базы.Производный объект больше не существует, поэтому его нельзя использовать.В этом случае в базовом классе resolved() является чисто виртуальным и не имеет тела.Вот почему это терпит крах так, как это происходит.Если у вас в базе был не-виртуальный resolved(), есть большая вероятность, что у вас будет другой сбой, так как объект уже был разрушен.

Обратите внимание, что даже если этот код сделалне сбой, потом, когда rv разрушен, вещи становятся грязными.Указатели внутри rv указывают на стек, но std::unique_ptr вызывает delete и предполагает, что объект находится в куче.Либо это немедленно завершится сбоем, поскольку это недопустимый указатель, куча / стек будет уничтожена, либо будет просто проигнорирована.Точное поведение неизвестно, так как это также неопределенное поведение.

Вот более простой случай, когда вы столкнетесь с подобной проблемой:

class Base
{
    public:
            virtual ~Base() { bar(); }
            virtual void foo() = 0;
            void bar() { foo(); }
};
class Derived: public Base
{
    public:
            void foo() override { };
};
int main()
{
    Derived b;
}
0 голосов
/ 28 октября 2018

rv[0]->solved();//gives pure virtual function called error

Конечно, это так.Ваша программа имеет неопределенное поведение, поэтому она может делать все что угодно.Также довольно легко разобрать этот фрагмент в причину проблемы:

MyClass *ptr;
{
  MyClass m;
  ptr = &m;
}
ptr->solved();

Как только мы избавимся от всех этих красных селедок, мы увидим, что все указатели в вашем rv контейнер указывает на объекты с автоматическими сроками хранения, которые с тех пор вышли из области видимости.Использование их для доступа к этому объекту будет вести себя каким-то неопределенным образом.

Если вы хотите, чтобы rv сохранял указатели-владельцы на копии this, затем создайте эти копии с продолжительностью динамического хранения

for(auto e:elements){
    MyClass& m = *this; // Assuming we need the reference binding
    if(m.xLargerthanY(x,y)){
        rv.push_back(make_unique<MyClass>(m)); 
    }
}

И теперь все указывает на действительные объекты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...