2017-04-22 50 views
-2

我有容器,其中包含std::vector<std::shared_ptr<Base> > VEC shared_ptr的基類Base,也該容器具有功能add(Base&& base) - 通過使用std::move運營商將基本對象,以VEC,但是派生轉移構造不調用,只調用基礎構造函數。的std ::招不來創建衍生的

如何解決:我應該將其轉換爲Derived,使用dynamic_cast<Derived*>(&base),之後使用std::move

問題:我是否需要嘗試轉換爲所有可能的派生類,然後使用std::move

class Base{ 
public: 
    virtual ~Base(){} 

    Base(const Base& base) = delete; 
    Base(Base&& base){ 
     std::cout<<"base move\n"; 
    } 
    Base(){} 

    virtual void func(){ 
     std::cout<<"run func from BASE class\n"; 
    } 
}; 
class Derived: public Base{ 
public: 
    virtual ~Derived() = default; 
    Derived(Derived&& derived):Base(std::move(derived)){ 
     std::cout<<"Derived move\n"; 
    } 
    Derived():Base(){ 

    } 

    virtual void func() override { 
     std::cout<<"run func from DERIVED class\n"; 
    } 
}; 
class Container{ 
    std::vector<shared_ptr<Base> > vec; 
public: 
    void add(Base&& base){ 
     this->vec.push_back(std::make_shared<Base>(std::move(base))); 
    } 
    void addDerived(Base&& base){ 
     //TRY ALL POSSIBLE CASTING??? 
     this->vec.push_back(std::make_shared<Derived>(std::move(*(dynamic_cast<Derived*>(&base))))); 
    } 

    void print(){ 
     for(auto& obj: vec){ 
      obj->func(); 
     } 
    } 
}; 
int main() { 
    std::cout << "Create container and add using function `add`" << std::endl; 
    Container container; 
    container.add(Derived()); 
    container.print(); 

    std::cout << "Create container and add using function `addDerived`" << std::endl; 
    Container container_new; 
    container_new.addDerived(Derived()); 
    container_new.print(); 
} 
//Will print 
Create container and add using function `add` 
base move 
run func from BASE class 
Create container and add using function `addDerived` 
base move 
Derived move 
run func from DERIVED class 

回答

1

這是一個糟糕的接口:

void add(Base&& base){ 
    this->vec.push_back(std::make_shared<Base>(std::move(base))); 
} 

你只是總是創造一個Base。你正在切斷基本子對象,這不是一個真正的多態拷貝。爲了做到這一點,你必須在Base上添加諸如clone()方法之類的東西。

但是將多態性留給用戶更簡單。你的工作只是提供一個安全的界面。這個接口應該是:

void add(std::shared_ptr<Base> p) { 
    vec.push_back(std::move(p)); 
} 

現在我作爲用戶,可以作爲界面設計師不必擔心它提供shared_ptr到我想要的任何派生類型,沒有你:

container.add(std::make_shared<MySuperCoolDerived>(42)); // cool 
+0

謝謝!但在目前的情況下,我無法從接口傳遞shared_ptr。我將值傳遞給我的構造函數,其中輸入參數是initializer_list。請檢查我的更新! –

0

你需要停止嘗試移動到開始的對象。如果你想擁有一個指向某個基類的指針的容器,但你不希望用戶直接分配你存儲的派生類,那麼它應該由你的插入函數來完成分配。在這種情況下,它應該像典型的標準庫emplace功能:

template<typename T, typename ...Args> 
void add(Args && ...args) 
{ 
    static_assert(std::is_base_of<Base, T>::value, "The type must be a base class of `Base`"); 
    static_assert(std::is_convertible<T&, Base&>::value, "The type must be an *accessible* base class of `Base`"); 
    this->vec.push_back(std::make_shared<T>(std::forward<Args>(args)...)); 
} 

... 

container.add<Derived>(); 

如果用戶擁有他們想要搬入由container創建了一個新Derived一個Derived的情況下,那麼他們會做container.add<Derived>(std::move(theirDerivedInstance));

+0

這是我對標準庫感到奇怪的事情之一。 'is_base_of'往往看起來像是錯誤的東西,因爲它會匹配隱私/模糊的基礎...而你可能想要的是'is_convertible ',它根本沒有傳達意圖...... – Barry

+0

@Barry:你需要兩者,因爲某些東西可以被轉換而不是真正的基礎。 –

+1

@NicolBolas如果您對指針而不是參考進行檢查,則不行。 –

1
template<class D> 
std::enable_if_t< std::is_convertible<std::decay_t<D>*, Base*>::value > 
add(D&& d){ 
    add(std::make_shared<std::decay_t<D>>(std::forward<D>(d))); 
} 
void add(std::shared_ptr<Base> base){ 
    this->vec.push_back(std::move(base)); 
} 
template<class D, class...Args> 
void emplace(Args&&...args){ 
    add(std::make_shared<D>(std::forward<Args>(args)...)); 
} 

現在有3種方式添加東西。

add(Derives())像你想要的那樣工作。 add(std::make_shared<Foo>(7))可讓您直接注入共享的ptrs。

emplace<Derived>(args...)可以讓你構建它。

添加了一個花哨的SFINAE檢查;我跳過它的地方。你可以做同樣的可檢查的檢查,並且如果D來自Args,則add是可構造的...另外,當我編寫emplace時,我有時會添加第一個參數是初始化器列表構造器,因爲它修復了完美轉發的常見缺陷。

+0

謝謝,「add」方法完美地工作!模板已完成其工作。但你可以檢查我的更新關於構造函數內的initializer_list。 –

+0

@AlfredHichkog如果您有新問題,請使用問題按鈕而不是編輯按鈕。 – Yakk