Переместить конструктор и переменные-члены const - PullRequest
23 голосов
/ 11 июня 2011

Мне нравится идея константных переменных-членов, особенно когда я оборачиваю функции C в классы.Конструктор берет дескриптор ресурса (например, дескриптор файла), который остается действительным в течение всего времени жизни объекта, и деструктор, наконец, закрывает его.(В этом и заключается идея RAII, верно?)

Но с конструктором перемещения C ++ 0x я столкнулся с проблемой.Поскольку деструктор также вызывается для «незагруженного» объекта, мне нужно предотвратить очистку дескриптора ресурса.Поскольку переменная-член const, у меня нет способа присвоить значение -1 или INVALID_HANDLE (или эквивалентные значения), чтобы указать деструктору, что он не должен ничего делать.

Есть ли способ, которым деструктор не являетсявызывается, если состояние объекта было перемещено в другой объект?

Пример:

class File
{
public:
    // Kind of "named constructor" or "static factory method"
    static File open(const char *fileName, const char *modes)
    {
        FILE *handle = fopen(fileName, modes);
        return File(handle);
    }

private:
    FILE * const handle;

public:
    File(FILE *handle) : handle(handle)
    {
    }

    ~File()
    {
        fclose(handle);
    }

    File(File &&other) : handle(other.handle)
    {
        // The compiler should not call the destructor of the "other"
        // object.
    }

    File(const File &other) = delete;
    File &operator =(const File &other) = delete;
};

Ответы [ 5 ]

15 голосов
/ 11 июня 2011

Вот почему вы не должны объявлять указанные переменные-члены const.const переменные-члены обычно не имеют смысла.Если вы не хотите, чтобы пользователи мутировали FILE*, не предоставляйте им функции, чтобы сделать это, и если вы хотите не допустить, чтобы они мутировали случайно, пометьте свои функции const.Однако не делайте сами переменные-члены const - потому что тогда вы столкнетесь с fun , когда начнете использовать семантику перемещения или копирования.

8 голосов
/ 11 июня 2011

Нет, нет способа сделать это. Я бы сказал, что если вы действительно привязаны к переменной handle const, у вас должна быть переменная-член неконстантного флага, которая указывает, должно ли уничтожение что-либо делать.

4 голосов
/ 11 июня 2011

Типичный способ реализации конструктора перемещения - обнулить или иным образом сделать недействительными члены перемещаемого экземпляра (простой пример см. MSDN ).Поэтому я бы сказал, просто не используйте const здесь, поскольку это несовместимо с целями семантики перемещения.

1 голос
/ 10 июня 2016

На самом деле, я тоже столкнулся с этой проблемой сегодня.Не желая принимать «не может быть сделано» и «использовать shared_ptr / подсчет ссылок», прибегая к помощи, я придумал этот базовый класс:

class Resource
{
private:
     mutable bool m_mine;

protected:
    Resource()
    : m_mine( true )
    {
    }

    Resource(const Resource&)       = delete;
    void operator=(const Resource&) = delete;

    Resource(const Resource&& other)
    : m_mine( other.m_mine )
    {
        other.m_mine = false;
    }

    bool isMine() const
    {
        return m_mine;
    }
};

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

class A : protected Resource
{
private:
    const int m_i;

public:
    A()
    : m_i( 0 )
    {
    }

    A( const int i )
    : m_i( i )
    {
    }

    A(const A&& a)
    : Resource( std::move( a     ) )
    , m_i     ( std::move( a.m_i ) ) // this is a move iff member has const move constructor, copy otherwise
    {
    }

    ~A()
    {
        if ( isMine() )
        {
            // Free up resources. Executed only for non-moved objects
            cout << "A destructed" << endl;
        }
    }
};

Поле (я) поля A теперь может быть постоянным.Обратите внимание, что я унаследовал защищенный, так что пользователь не может случайно привести A к Ресурсу (или очень охотно взломать его), но A все еще не является окончательным, поэтому вы все еще можете наследовать от него (действительная причина наследовать от Resourceнапример, иметь отдельный доступ для чтения и чтения-записи).Это один из крайне редких случаев, когда защищенное наследование не означает автоматически, что ваш дизайн неисправен;однако, если вам трудно это понять, вы можете просто использовать публичное наследование.

Затем, если у вас есть struct X:

struct B
{
    const A m_a;
    const X m_x;

    B(const A&& a, const X& x) // implement this way only if X has copy constructor; otherwise do for 'x' like we do for 'a'
    : m_a( std::move( a ) )
    , m_x(            x   )
    {
    }

    B( const B&& b )
    : m_a( std::move( b.m_a ) )
    , m_x( std::move( b.m_x ) ) // this is a move iff X has move constructor, copy otherwise
    {
    }

    ~B()
    {
        cout << "B destructed" << endl;
    }
};

Обратите внимание, что поля B также могут бытьУст.Наши конструкторы ходов постоянны.Если у ваших типов есть соответствующие конструкторы перемещения, любая выделенная куча память может быть распределена между объектами.

0 голосов
/ 11 июня 2011

Подсчет ссылок - это стандартный подход, который решает вашу проблему. Попробуйте добавить подсчет ссылок в свой класс; либо вручную, либо с использованием существующих инструментов, таких как boost shared_ptr.

...