2015-12-20 3 views
1

Скажут, у меня есть два класса Foo и Bar наследуя интерфейс Base и хотим хранить экземпляры этих классов в векторе типа std::vector<std::unique_ptr<Base>>. Объект типа Bar должен хранить ссылку на значение (которое в этом случае является членом класса Foo). Концептуально, я хочу, чтобы достичь следующего:Недействительные ссылки на член полиморфных объектов

#include <iostream> 
#include <memory> 
#include <vector> 

template<typename Type, typename ... Types> 
std::unique_ptr<Type> make_unique(Types &&... arguments) { 
    return std::unique_ptr<Type>(new Type(std::forward<Types>(arguments)...)); 
} 

class Base { 
public: 
    virtual ~Base() = 0; 
    virtual void execute() = 0; 
}; 

Base::~Base() { 
} 

class Foo: public Base { 
public: 
    void execute() override { 
     // Foo-specific implementation of execute(), may change value_ 
     value_ = 2; 
    } 
    const int & value() const { 
     return value_; 
    } 
private: 
    int value_; 
}; 

class Bar: public Base { 
public: 
    Bar(const int &value) : value_(value) {} 
    void execute() override { 
     // Bar-specific implementation of execute(), uses current value_ 
     std::cout << value_ << std::endl; 
    } 
private: 
    const int &value_; // Reference to a value, not necessarily from Foo 
}; 

int main() { 
    // Collection of base objects, possibly inside another object 
    std::vector<std::unique_ptr<Base>> baseVector; 
    baseVector.emplace_back(make_unique<Foo>()); 
    baseVector.emplace_back(make_unique<Bar>(
     dynamic_cast<Foo *>(baseVector.back().get())->value())); 
    for (auto &base : baseVector) 
     base->execute(); 

    return 0; 
} 

Однако dynamic_cast чувствует себя очень вонючий ко мне (я знаю, что я мог бы также использовать static_cast, но это не намного лучше, я думаю). Альтернативой избежать dynamic_cast бы изменить Foo класс выглядеть

class Foo: public Base { 
public: 
    Foo() : value_(new int()) {} 
    void execute() override { 
     // Foo-specific implementation of execute(), may change value_ 
     *value_ = 2; 
    } 
    const int & value() const { 
     return *value_; 
    } 
private: 
    std::unique_ptr<int> value_; 
}; 

, а затем сделать

int main() { 
    // Collection of base objects, possibly inside another object 
    std::vector<std::unique_ptr<Base>> baseVector; 
    auto foo = make_unique<Foo>(); 
    auto *fooPtr = foo.get(); 
    baseVector.emplace_back(std::move(foo)); 
    baseVector.emplace_back(make_unique<Bar>(fooPtr->value())); 
    for (auto &base : baseVector) 
     base->execute(); 

    return 0; 
} 

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

ответ

1

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

struct Base { virtual ~Base() = default; /* ...*/ }; 
struct Foo : Base { int x_; int & value() { return x_; /* ...*/ }; 
struct Bar : Base { int & r_; Bar(int & r) : r_(r) {} /* ...*/ }; 

auto foo = std::make_unique<Foo>(); 
auto bar = std::make_unique<Bar>(foo->value()); 

v.push_back(std::move(foo)); 
v.push_back(std::move(bar)); 

Просто потому, что emplace_back существует вовсе не означает, что вы должны использовать его для абсолютно все!

+0

['std :: make_unique'] (http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) - это C++ 14. – Zereges

+0

@Zereges: Поскольку 'std :: make_unique' является C++ 14, я предоставил свою собственную реализацию' make_unique'. – Marcel

+0

@ Kerrek SB: Хорошо, теперь вы выглядите немного более элегантно. Но ваше решение, а также моя вторая альтернатива требуют, чтобы член 'value_' класса' Foo' имел тип 'std :: unique_ptr '. В противном случае ссылка станет недействительной, как только экземпляр «Foo» будет перенесен в вектор. Является ли 'std :: unique_ptr' действительно моим единственным вариантом здесь? – Marcel

Смежные вопросы