Обработка умных указателей в контейнере STL - PullRequest
1 голос
/ 12 декабря 2011

У меня есть класс Foo<T>, который имеет вектор умных указателей на Shape производные классы. Я пытаюсь реализовать функцию-член at(index). Вот что я хотел бы сделать интуитивно:

Foo<float> myfoo;
std::unique_ptr<Shape<float>> shape_ptr = myfoo.at(i);
shape_ptr->doSomething(param1, param2, ...);

При определении функции at(index) я получаю сообщение об ошибке компилятора. Обратите внимание, что конструктор перемещения был определен и базовый класс Shape является абстрактным. Ниже я даю код для иллюстрации.

Кроме того, недавно я нашел в Интернете пример того, как перегрузить оператор присваивания с помощью std::move. Я обычно следую идиоме Copy-Swap. Какой из этих двух способов перегрузки упомянутого оператора имеет смысл для моего случая? Ниже я также иллюстрирую определение функции.

template < typename T >
class Foo{

    public:

        Foo();
        Foo( Foo && );
        ~Foo();

        void swap(Foo<T> &);
        //Foo<T> & operator =( Foo<T> );
        Foo<T> & operator =( Foo<T> && );

        std::unique_ptr<Shape<T> > at ( int ) const; // error here!

        int size() const;

    private:

        std::vector< std::unique_ptr<Shape<T> > > m_Bank;
};

template < typename T >
Foo<T>::Foo( Foo && other)
    :m_Bank(std::move(other.m_Bank))
{

}

/*template < typename T >
void Filterbank<T>::swap(Filterbank<T> & refBank ){

    using std::swap;
    swap(m_Bank, refBank.m_Bank);
}

template < typename T >
Foo<T> & Filterbank<T>::operator =( Foo<T> bank ){

    bank.swap(*this);
    return (*this);
}*/

template < typename T >
Foo<T> & Foo<T>::operator =( Foo<T> && bank ){

    //bank.swap(*this);
    m_Bank = std::move(bank.m_Bank);
    return (*this);
}

template < typename T >
std::unique_ptr<Shape<T> > Foo<T>::at( int index ) const{
    return m_Bank[index]; // Error here! => error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'
}

Ответы [ 4 ]

2 голосов
/ 13 декабря 2011

Использовать Контейнеры-указатели Boost вместо стандартного контейнера с unique_ptr.Они предназначены для такого использования.

1 голос
/ 13 декабря 2011

Q1: что делать с Foo::at( int ) const, чтобы вы могли:

myfoo.at(i)->doSomething(param1, param2, ...);

без передачи права собственности на vector<unique_ptr<Shape<T>>>.

A1: Foo::at( int ) const должен вернутьconst std::unique_ptr<Shape<T> >&:

template < typename T >
const std::unique_ptr<Shape<T> >&
Foo<T>::at( int index ) const
{
    return m_Bank[index];
}

Теперь вы можете разыменовать const unique_ptr и вызывать любого члена Shape, которого они хотят (const или non-const).Если они случайно попытаются скопировать unique_ptr (что приведет к передаче права собственности из Foo), они получат ошибку времени компиляции.

Это решение лучше, чем возвращение неконстантной ссылки на unique_ptr так как он ловит случайные переходы собственности из Foo.Однако, если вы хотите разрешить передачу прав собственности из Foo через at, тогда неконстантная ссылка будет более уместной.

Q2: Кроме того, недавно я нашел в Интернете пример того, какперегрузить оператор присваивания, используя std :: move.Я обычно следую идиоме Copy-Swap.Какой из этих двух способов перегрузки упомянутого оператора имеет смысл для моего случая?

A2: Я не уверен, что делает ~Foo().Если он ничего не делает, вы можете удалить его, а затем (при условии полного соответствия C ++ 11) вы автоматически получите правильный и оптимальный конструктор перемещения и оператор присваивания перемещения (и правильную семантику удаленной копии).

Если вы не можете удалить ~Foo() (потому что он делает что-то важное), или если ваш компилятор еще не реализует автоматическую генерацию перемещения, вы можете указать их явно, как вы это сделали в своем вопросе.

Ваш конструктор перемещения находится на месте: Переместите конструкцию члена.

Ваше назначение перемещения должно быть аналогичным (и это то, что будет автоматически генерироваться, если ~Foo() неявно): Переместить назначить члена:

template < typename T >
Foo<T> & Foo<T>::operator =( Foo<T> && bank )
{
    m_Bank = std::move(bank.m_Bank);
    return (*this);
}

Ваш Foo дизайн также может быть Swappable, и это всегда хорошо:

friend void swap(Foo& x, Foo& y) {x.m_Bank.swap(y.m_Bank);}

Без этого явного swap ваш Foo все еще Swappable, используя Foo конструктор перемещения и назначение перемещения.Однако этот явный swap примерно в два раза быстрее неявного.

Приведенный выше совет направлен на получение максимальной производительности из Foo.При желании вы можете использовать идиому Copy-Swap в своем задании на перемещение.Это будет правильно и немного медленнее.Хотя, если вы действительно будете осторожны, вы не получите бесконечную рекурсию с swap, вызывающим перемещение, и перемещением, вызывающим swap!:-) Действительно, эта ошибка - еще одна причина для чистого (и оптимального) разделения swap и перемещения назначения.

Обновление

Предполагается, что Shape выглядит this , вот один из способов кодирования конструктора перемещения, назначения перемещения, конструктора копирования и операторов копирования для Foo, при условии, что Foo имеет один элемент данных:

std::vector< std::unique_ptr< Shape > > m_Bank;

...

Foo::Foo(Foo&& other)
    : m_Bank(std::move(other.m_Bank))
{
}

Foo::Foo(const Foo& other)
{
    for (const auto& p: other.m_Bank)
        m_Bank.push_back(std::unique_ptr< Shape >(p ? p->clone() : nullptr));
}

Foo&
Foo::operator=(Foo&& other)
{
    m_Bank = std::move(other.m_Bank);
    return (*this);
}

Foo&
Foo::operator=(const Foo& other)
{
    if (this != &other)
    {
        m_Bank.clear();
        for (const auto& p: other.m_Bank)
            m_Bank.push_back(std::unique_ptr< Shape >(p ? p->clone() : nullptr));
    }
    return (*this);
}

Если ваш компилятор поддерживает заданные по умолчанию элементы перемещения, то же самое можно сделать с помощью:

    Foo(Foo&&) = default;
    Foo& operator=(Foo&&) = default;

для конструктора перемещения и оператора присваивания перемещения.

Вышеуказанное гарантирует, что каждый Shape всегда принадлежит только одному интеллектуальному указателю / вектору / Foo.Если вы предпочитаете, чтобы владение несколькими Foo s принадлежало Shape s, тогда вы можете иметь в качестве члена данных:

std::vector< std::shared_ptr< Shape > > m_Bank;

И вы можете по умолчанию использовать конструктор перемещения, назначение перемещения, конструктор копированияи скопируйте назначение.

1 голос
/ 12 декабря 2011

Я думаю, что вы должны использовать shared_ptr здесь вместо

Только один unique_ptr может владеть общим ресурсом. Если вы можете делать то, что намереваетесь, то есть возвращать unique_ptr по значению, тогда значение в векторе будет уничтожено , что, вероятно, вам не нужно.

0 голосов
/ 13 декабря 2011

Похоже, вы просто должны вернуть ссылку сюда:

Shape<T> & Foo<T>::at( int index ) const{
    return *m_Bank[index];
}
...