-1

copy/move ctors/assignment operator と dtor (Rule of Five) の実装を使用して、抽象基本クラスに関連するエラーC2248を解決しようとすると、いくつかの質問が出てきます。

1) unique_ptr データ メンバーが自動的に処理されるときに、主に dtor に関連する 5 のルールが適用されるのはなぜですか? 所有者が範囲外になると unique_ptrs が自動的に破棄されるため、dtor の実装は空のままにしておく必要がありますか?

2) 別のクラスが、同じ型のベクトルの型 std::unique_ptr のメンバーを持っているとします。このクラスをコピー可能にするためには、unique_ptr データ メンバーを複製するコピー ctor とコピー代入演算子が必要ですか? 私はこの解決策を見てきましたが、所有権管理をほとんど考慮せずにエラーを単独で削除するために、元のポスターが shared_ptr に切り替えたようです。これは正しい戦略ですか?

3) unique_ptr のベクトルに関連する上記の質問 2 と同じケースを検討してください。ベクトルを clear() する呼び出しを dtor に含める必要がありますか?

4) Derived1 の代入演算子が正しくありません。ただし、基本クラスにはコピー/移動 ctors (4/5 の規則) があるため、コピーおよび移動代入演算子があるはずです。これらは抽象的であり、インスタンスが割り当てられないため、クラスの外で実際に使用することはできません。しかし、派生クラスからこのコードを利用するにはどうすればよいでしょうか? 各派生クラスは、基本データ メンバーと独自のデータ メンバーを移動/コピーできる必要があります。どうすればいいのかわからない。

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

class Base{

public:
    Base() : m_subBases(){};

    /*  copy ctor */
    Base(const Base& other) : m_subBases(){
        *this = other;
    };

    /*  move ctor */
    Base(Base&& other) : m_subBases(){
        *this =std::move( other);
    };

    /*  Move assignment operator*/
    Base& operator=(Base&& other){
        m_subBases = std::move(other.m_subBases);
        return *this;
    };

    /*  Copy assignment operator */
    Base& operator=(const Base& other){
        for(int i = 0; i < other.m_subBases.size(); i++)
            m_subBases.push_back(other.m_subBases[i]->clone());

        return *this;
    };

    /* virtual dtor */
    virtual ~Base(){
        m_subBases.clear();
    };

    /* Used for creating clones of unique_ptrs */
    virtual std::unique_ptr <Base> clone() const= 0;

    /* Do something */
    virtual void execute(float f) = 0;

    //Omitted data member access methods

protected:
    std::vector < std::unique_ptr <Base> > m_subBases;
};

class Derived1 : public Base{

public:
    Derived1() :  Base(){};

    /*  copy ctor */
    Derived1(const Derived1& other) : Base(other){
        *this = other;
    };

    /*  move ctor */
    Derived1(Derived1&& other) : Base(std::move(other)){
        *this = std::move(other);
    };

    /*  Move assignment operator*/
    Derived1& operator=(Derived1&& other){

        //This is redundant when called in the move ctor because
        // of the call to Base(std::move(other))
        m_subBases = std::move(other.m_subBases);

        m_string = other.m_string;
        return *this;
    };

    /*  Copy assignment operator */
    Derived1& operator=( const Derived1& other){

        //This is redundant when called in the copy ctor because
        // of the call to Base(other)
        for(int i = 0; i < other.m_subBases.size(); i++)
            m_subBases.push_back(other.m_subBases[i]->clone());

        m_string = other.m_string;
        return *this;
    };

    /* virtual dtor */
    virtual ~Derived1(){};

    /* Used for creating clones of unique_ptrs */
    virtual std::unique_ptr <Base> clone() const{
        return std::unique_ptr <Base> (new Derived1(*this));
    };

    virtual void execute(float f){
        std::cout << "Derived1 " << f << std::endl; 
    };
protected:

    std::string m_string;
};
4

1 に答える 1

2

I'd like to offer an alternative approach. Not the Scary Rule of Five, but the Pleasant Rule of Zero, as @Tony The Lion has already suggested. A full implementation of my proposal has been coded by se­ve­ral people, and there's a fine version in @R. Martinho Fernandes's library, but I'll present a simplified version.

First, let's recap:

The Rule of Zero: Don't write a copy- or move-constructor, a copy- or move-assignment ope­ra­tor, or a destructor. Instead, compose your class of components which handle a single responsibility and encap­su­late the desired behaviour for the individual resource in question.

There's an obvious caveat: When you design the single-responsibility class, you must of course obey:

The Rule of Five: If you write any one of copy- or move-constructor, copy- or move-assignment ope­ra­tor, or destructor, you must implement all five. (But the "five" functions needed by this rule are actually: Destructor, Copy-Const, Move-Const, Assignment and Swap.)

Let's do it. First, your consumer:

struct X;

struct Base
{
    std::vector<value_ptr<X>> v;
};

struct Derived : Base
{
};

Note that both Base and Derived obey the Rule of Zero!

All we need to do is implement value_ptr. If the pointee is non-polymorphic, the following will do:

template <typename T>
class value_ptr
{
    T * ptr;
public:
    // Constructors
    constexpr value_ptr()      noexcept : ptr(nullptr) { }
    constexpr value_ptr(T * p) noexcept : ptr(p)       { }

    // Rule of Five begins here:
    ~value_ptr() { ::delete ptr; }
    value_ptr(value_ptr const & rhs) : ptr(rhs.ptr ? ::new T(*rhs.ptr) : nullptr) { }
    value_ptr(value_ptr && rhs) noexcept : ptr(rhs.ptr) { rhs.ptr = nullptr; }
    value_ptr & operator=(value_ptr rhs) { swap(rhs); return *this; }
    void swap(value_ptr & rhs) noexcept { std::swap(rhs.ptr, ptr); }

    // Pointer stuff
    T & operator*() const noexcept { return *ptr; }
    T * operator->() const noexcept { return ptr; }
};

template <typename T, typename ...Args>
value_ptr<T> make_value(Args &&... args)
{
    return value_ptr<T>(::new T(std::forward<Args>(args)...));
}

If you would like smart pointer that handles polymorphic base class pointers, I suggest you demand that your base class provide a virtual clone() function, and that you implement a clone_ptr<T>, whose copy constructor would be like this:

clone_ptr(clone_ptr const & rhs) : ptr(rhs.ptr ? rhs.ptr->clone() : nullptr) { }
于 2012-09-18T16:33:09.253 に答える