Замена некопируемого, неподвижного объекта

Рассмотрим следующий код:

// A non-copyable, non-movable aggregate
struct Strange
{
    const int & i;
    char & c;
};

class Container
{
    private:
        int  my_i;
        char my_c;
        Strange thing;

    public:
        // Valid, because both `my_i´ and `my_c´ are non-const
        // objects to which both references can be bound.
        explicit
            Container
            ( )
            noexcept
            : thing{ my_i , my_c }
            { }

        // How could this be implemented?
        auto &
            operator=
            ( const Container & that )
            noexcept
            {
                this->my_i = that->my_i;
                this->my_c = that->my_c;

                // What to do with `thing´?

                return *this;
            }
};

Возможные решения

  1. Динамическое размещение объекта Strange

    class Container
    {
        private:
            int  my_i;
            char my_c;
            Strange * thing;
    
        public:
            // Note that it isn't exception safe.
            explicit
                Container
                ( )
                : thing(new Strange{ my_i , my_c })
                { }
    
            auto &
                operator=
                ( const Container & that )
                noexcept
                {
                    this->my_i = that->my_i;
                    this->my_c = that->my_c;
    
                    delete this->thing;
                    this->thing = new Strange { this->my_i , this->my_c };
    
                    return *this;
                }
    };
    

    Обеспокоенность:

    • Not efficient.
    • Небезопасно: выделение может завершиться ошибкой и бросить вызов.
    • Опасно: необходимо проявлять большую осторожность, чтобы не допустить утечки памяти.

      Использование интеллектуального указателя (например, std::unique_ptr) решит только последнюю точку, не говоря уже о том, что код станет более читаемым.

  2. Использовать новое место размещения

    class Container
    {
        private:
            int  my_i;
            char my_c;
            Strange thing;
    
        public:
            explicit
                Container
                ( )
                noexcept
                : thing{ my_i , my_c }
                { }
    
            auto &
                operator=
                ( const Container & that )
                noexcept
                {
                    this->my_i = that.my_i;
                    this->my_c = that.my_c;
    
                    // Placement new is exception safe, and so is
                    // construction of `Strange´.
                    this->thing.~Strange();
                    new(&this->thing) Strange { this->my_i , this->my_c };
    
                    return *this;
                }
    };
    

    Обеспокоенность:

    • Освободит ли деструктор Strange память, занятую thing?

      Я думаю, что так же, как и конструкторы, деструкторы не отвечают за управление памятью. Более того, мой код работает нормально. Однако хотелось бы пояснений по этому поводу.

    • Как насчет выравнивания памяти?

      Я предполагаю, что, поскольку он заменяет существующий объект того же типа, память уже будет выровнена. Это верно?

    • Позаботится ли деструктор Container об уничтожении thing?

Вопросы

Помимо подтверждения и/или опровержения опасений, которые я изложил выше, я хотел бы знать, есть ли другие альтернативы. Если да, то приведите пример реализации.


Этот вопрос возник при работе над классом, который должен предлагать интерфейс, аналогичный интерфейсу std::unordered_map. Вместо того, чтобы переопределять его, мой класс инкапсулирует такой контейнер и просто действует как прокси для большинства методов: его итераторы охватывают итераторы, предоставляемые картой, а его пара представляет собой агрегированную структуру с членами с соответствующими именами (которые являются ссылками на фактические данные), представленные в приведенных примерах как Strange. Поскольку итераторы должны возвращать ссылки и указатели на фактические данные, мои пользовательские итераторы содержат пару. Проблема заключалась в его изменении (при увеличении или назначении итератора). Я признаю, что это, вероятно, не очень хорошая идея, и что эти ссылки повлияют на производительность, но меня все равно интересует этот вопрос.

Редактировать

Я только что понял, что вместо того, чтобы возвращать ссылки и указатели на пользовательскую пару элементов, указывающую на фактические данные (инкапсулированную карту) из моего пользовательского итератора, я мог бы возвращать созданные на месте пользовательские пары (т.е. Strange объекты). Часто мы не видим, что находимся в пещере, и вместо того, чтобы выйти из нее, продолжаем идти вперед :). Извините за шум, я отмечу вопрос как "Закрытый".


person Kalrish    schedule 01.09.2014    source источник
comment
Поскольку thing содержит ссылки на самих себя, в опубликованном вами коде вам не нужно ничего делать с ним в операторе присваивания копии.   -  person T.C.    schedule 01.09.2014
comment
@Т.С. Вы правы, мой пример сломан. Чтение последнего абзаца может помочь понять мою проблему, а я попытаюсь привести лучший пример.   -  person Kalrish    schedule 01.09.2014
comment
AFAIK, новое размещение требует, чтобы вы вызывали деструктор вручную, если это необходимо. Иначе не чистит. Кроме того, не могли бы вы объявить конструкторы копирования и перемещения удаленными?   -  person sukhmel    schedule 01.09.2014
comment
@sukhmel Спасибо, я так и подозревал :). Оператор присваивания и изменение Container в целом необходимы, потому что на самом деле это пользовательский итератор, обертывающий std::unordered_map. Поскольку я также хотел предоставить свои собственные пары с именованными элементами, итератор также должен был их инкапсулировать. Но ссылки должны быть изменены при увеличении итератора.   -  person Kalrish    schedule 01.09.2014
comment
@Kalrish: Если вы действительно хотите изменить то, на что они ссылаются, вам, вероятно, лучше использовать указатели, а не пытаться заставить ссылки вести себя иначе, чем ссылки.   -  person Mike Seymour    schedule 01.09.2014
comment
@MikeSeymour Я хотел сохранить согласованность (интерфейс моего пользовательского контейнера должен быть одинаковым, независимо от того, как он реализован): если бы я реализовал карту самостоятельно, мои пользовательские пары должны были быть фактическими данными, а указатели больше не подходили бы. Но я только что понял, что этот вопрос абсурден, и отредактировал его соответствующим образом. Спасибо вам всем! :)   -  person Kalrish    schedule 01.09.2014
comment
Этот вопрос кажется не по теме, потому что он касается проблемы, вызванной (плохим) решением другой проблемы. Последняя проблема исчезнет при правильном решении основной.   -  person Kalrish    schedule 01.09.2014


Ответы (1)


(Если мы говорим о перемещении объектов и использовании ключевого слова auto, вы должны добавить к своему вопросу тег c++11).

Я не уверен, что действительно понимаю ваш вопрос; пример, который вы привели, кажется мне непродуманным; было бы намного лучше использовать указатели в Strange. Например, это компилируется и работает абсолютно нормально и функционально эквивалентно тому, что вы хотите сделать, я думаю.

struct Strange
{
    Strange()
        : i(nullptr), c(nullptr) {}
    Strange( const int *_i, const char *_c )
        : i(_i), c(_c) {}

    const int *i;
    const char *c;
};

class Container
{
    int  my_i;
    char my_c;
    Strange thing;

    public:

        Container()
            : thing(&my_i,&my_c)
            { }

        Container( int i, char c )
            : my_i(i), my_c(c), thing(&my_i,&my_c)
            { }

        Container( int i, char c, const Strange& s )
            : my_i(i), my_c(c), thing(s) // use default copy-constructor
            { }

        Container &
            operator=
            ( const Container & that )
            {
                my_i  = that.my_i;
                my_c  = that.my_c;
                thing = that.thing;

                return *this;
            }
};

int main()
{
    Container a(12,24);
    Container b(25,42);
    b = a;
}

Обратите внимание, что обращение к памяти внутри объектов, как правило, опасно. Например, использование memcpy для этого было бы катастрофой. (скомпилировано с помощью clang и g++)

person Jonathan H    schedule 01.09.2014
comment
Вы упустили мою мысль о том, что Strange должен содержать ссылки - как я объяснил в комментарии выше, для обеспечения согласованности - но это была моя вина, что я не предоставил подходящий пример. В качестве совершенно не относящейся к делу заметки вы должны использовать NULL или (предпочтительно) nullptr вместо 0. Спасибо за ответ :). - person Kalrish; 01.09.2014
comment
Я не был уверен в nullptr, потому что не было тега С++ 11. Тогда мой вопрос к вам: почему бы не использовать указатели? - person Jonathan H; 01.09.2014
comment
Я отредактировал вопрос, как вы предложили. Что касается вашего вопроса, я работаю над собственным классом с функциональностью, аналогичной std::unordered_map. Проблема возникает с итераторами и парами. Я хочу, чтобы в моих парах были именованные члены, т. е. не first и second. Для этого мои пользовательские пары просто содержат ссылки — таким образом, они похожи на настоящие пары. Но поскольку итераторы должны возвращать ссылки и указатели (оператор* и оператор-›), эти пользовательские пары должны где-то храниться (в каждом итераторе). И когда он увеличивается или копируется... - person Kalrish; 01.09.2014
comment
Значит, все эти проблемы только для того, чтобы назвать членов std::pair по-другому? Как вы думаете, оно того стоит? Вы можете просто определить роль в контексте, в котором вы обрабатываете итераторы, которые предоставляют другое пространство имен (буквально, а не С++). Думаю будет проще и чище. :) - person Jonathan H; 01.09.2014
comment
Поскольку это не что-то профессиональное, это так же приятно, как и приятно. Итак... Более или менее. Во всяком случае, должен быть способ назвать их по-другому. Они не очень описательные. - person Kalrish; 01.09.2014