Segmentation fault в реализации класса Any

189
29 августа 2018, 12:50

В процессе изучения C++ решал вот эту задачу. Если кратко, то экземпляры нешаблонного класса Any (который и нужно реализовать) должны иметь способность хранить значение любого типа, но об условии задания по ссылке, оно весьма объёмное.

Был написан следующий код (IClonable и ValueHolder здесь определены для полноты картины, в самом задании это не требуется, т.к. было реализовано раньше):

struct ICloneable
{
    virtual ICloneable* clone() const = 0;
    virtual ~ICloneable() { }
};
template <typename T>
struct ValueHolder : public ICloneable
{
    T data_;
    ValueHolder(const T& obj) : data_(obj) {}
    ValueHolder* clone() const
    {
        return new ValueHolder(data_);
    }
};
class Any
{
    ICloneable* data_;
public:    
    Any(): data_(0) {};
    ~Any() { delete data_; }
    template <typename T>
    Any(T obj) : data_(new ValueHolder<T>(obj)) {};     
    Any(const Any& obj)
    {
        if (!obj.data_) data_ = 0;
        data_ = obj.data_->clone();
    }
    template <typename T>
    Any& operator=(const T & obj)
    {
        delete data_;
        data_ = new ValueHolder<T>(obj);
        return *this;
    }
    Any& operator=(const Any& obj)
    {
        if (this != &obj)
        {
            if (!obj.data_) data_ = 0;
            // if (!obj.data_) return; работающая версия
            delete data_;
            data_ = obj.data_->clone();
        }
        return *this;
    }
    template <typename T>
    T* cast()
    {
        if (!data_) return 0;
        ValueHolder<T>* cast_try = dynamic_cast< ValueHolder<T>* >(data_);
        if (!cast_try) return 0;
        return &(cast_try->data_);
    }
};

Тестирующая система по непонятным мне причинам указывала на ошибку сегментации. После многочисленных танцев с бубном в конструкторе копирования я поменял if (!obj.data_) data_ = 0; на if (!obj.data_) return; и все по не менее непонятным мне причинам заработало.

Но дальше самое интересное, в решениях пользователей я вижу вот такую реализацию:

#include <utility>
struct ICloneable;
template <typename T>
struct ValueHolder;
struct Any {
    Any() : data_(0) {}
    ~Any() { delete data_; }
    Any(const Any& other) : data_(other.data_ ? other.data_->clone() : 0) {}
    template <typename T> Any(const T& value) : data_(new ValueHolder<T>(value)) {}
    template <typename T> T* cast() 
    {
        ValueHolder<T>* castedValue;
        return data_ && (castedValue = dynamic_cast< ValueHolder<T>* >(data_)) ? &(castedValue->data_) : 0;
    }
    Any & operator=(const Any & other) 
    {
        if(this != &other) 
        {
            Any tmp(other);
            std::swap(data_, tmp.data_);
        }
        return *this;
    }
private:
    ICloneable* data_;
};

Можно заметить, что конструктор копирования и вообще большинство методов (кроме оператора присваивания) реализованы аналогично. Вопросы: 1. Почему не работает первая версия кода? 2. Почему вдруг работает вторая? 3. В чем разница между первой версией моего кода и кодом, который я увидел в решении и почему эта разница позволяет этому коду работать?

Закрадываются подозрения, что из-за удаления data_ в операторе присваивания могла произойти какая-то некорректная операция + возможно дело в двух версиях оператора присваивания в моем коде против одной версии в коде чужом.

Но это только смутные догадки и, даже если они верны, я был бы рад получить ответ что именно происходит и как этого избежать в будущем. (то есть преимущества реализации оператора присваивания в чужом коде в данном конкретном случае я уже оценил, но хотелось бы полного понимания процесса)

Уже заранее большое спасибо, что дочитали и пытаетесь въехать в эту проблему!

Answer 1

В первой версии у вас разыменовывание нулевого указателя когда obj.data_ нулевой, а во второй нет.

if (!obj.data_) data_ = 0;
data_ = obj.data_->clone();

Если бы код был лучше отформатирован, то ошибка была бы более заметна:

if(!obj.data_)
{
   data_ = 0;
}
data_ = obj.data_->clone();

Вместо data_ = 0; следовало бы написать data_ = nullptr;, присвоение нулевого литерала указателям - это атавизм.

Ну и вообще-то стоило бы поотлаживать программу, а не гадать.

READ ALSO
Ubuntu, gcc, сборка so, не работают try..catch

Ubuntu, gcc, сборка so, не работают try..catch

Собираю библиотеку Libso (Ubuntu, gcc 5

158
Арканоид на c++

Арканоид на c++

Задали написать арканоидВозникла проблема с моментом физики поведения мяча, мяч и дощечка ( от которой он отскакивает ) представляют собой...

561
Поиск файла в папке ресурсов

Поиск файла в папке ресурсов

Есть проект с архитектурой:

160
Объект canvas не прорисовывается при касании

Объект canvas не прорисовывается при касании

Не получается сделать так, чтобы при касании на экран появляется объект canvas цифра 8 в точке нажатияПомогите исправить ошибку

205