std:: вектор объектов и константность

Учтите следующее:

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    :   c(_c)
    {
        // Nothing here
    }

    A(const A& copy)
    : c(copy.c)
    {
        // Nothing here
    }    
};



int main(int argc, char *argv[])
{
    A foo(1337);

    vector<A> vec;
    vec.push_back(foo); // <-- compile error!

    return 0;
}

Очевидно, что конструктор копирования не достаточно. Что мне не хватает?

РЕДАКТИРОВАТЬ: Ofc. Я не могу изменить this->c в методе operator=(), поэтому я не вижу, как будет использоваться operator=() (хотя это требуется для std::vector).

9 ответов

Решение

Я не уверен, почему никто не сказал это, но правильный ответ - отбросить const или магазин A* находится в векторе (используя соответствующий умный указатель).

Вы можете придать своему классу ужасную семантику, заставив "копировать" вызывать UB или ничего не делая (и, следовательно, не будучи копией), но почему все эти проблемы танцуют вокруг UB и плохого кода? Что вы получаете, делая это const? (Подсказка: ничего.) Ваша проблема концептуальна: если у класса есть постоянный член, класс является константным. Объекты, которые являются const, принципиально не могут быть назначены.

Просто сделайте его неконстантным частным и обнажите его ценность. Для пользователей это эквивалентно, по-постоянному. Это позволяет неявно сгенерированным функциям работать просто отлично.

Контейнерный элемент STL должен быть копируемым и назначаться 1 (который ваш класс A нет). Вам нужно перегрузить operator =,

1: §23.1 говорит The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignabletypes


РЕДАКТИРОВАТЬ:

Отказ от ответственности: я не уверен, является ли следующий фрагмент кода на 100% безопасным. Если это вызывает UB или что-то еще, пожалуйста, дайте мне знать.

A& operator=(const A& assign)
{
    *const_cast<int*> (&c)= assign.c;
    return *this;
}

РЕДАКТИРОВАТЬ 2

Я думаю, что приведенный выше фрагмент кода вызывает неопределенное поведение, потому что пытается отбросить постоянство const квалифицированная переменная вызывает UB,

Вам не хватает оператора присваивания (или оператора копирования), одного из большой тройки.

Сохраненный тип должен соответствовать требованиям CopyConstructible и Assignable, что означает, что operator= также необходим.

Вероятно, оператор присваивания. Компилятор, как правило, генерирует для вас компилятор по умолчанию, но эта функция отключена, поскольку ваш класс имеет нетривиальную семантику копирования.

Я думаю, что для реализации векторной функции в STL требуется оператор присваивания (см. Цитату Прасуна из Стандарта). Однако согласно приведенной ниже цитате, поскольку оператор присваивания в вашем коде неявно определен (так как он не определен явно), ваша программа имеет плохую форму из-за того, что ваш класс также имеет константный нестатический член данных.

C++ 03

$ 12.8 / 12 - "Неявно объявленный оператор присваивания копии неявно определяется, когда объекту его типа класса присваивается значение его типа класса или значение типа класса, производного от его типа класса. Программа плохо информируется, если класс для которого неявно определен оператор присваивания копии:

- нестатический элемент данных типа const, или

- нестатический элемент данных ссылочного типа, или

- нестатический член данных типа класса (или его массив) с оператором недоступного копирования, или

- базовый класс с недоступным оператором присваивания копии.

Вам также нужно реализовать конструктор копирования, который будет выглядеть так:

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    ...

    A(const A& copy)
    ...  

    A& operator=(const A& rhs)
    {
        int * p_writable_c = const_cast<int *>(&c);
        *p_writable_c = rhs.c;
        return *this;
    }

};

Специальный const_cast Шаблон принимает тип указателя и возвращает его в форму, доступную для записи, для таких случаев, как эта.

Следует отметить, что const_cast не всегда безопасно использовать, см. здесь.

Недавно я столкнулся с такой же ситуацией и вместо этого использовал std::set, потому что его механизм добавления элемента (insert) не требует оператора = (использует оператор <), в отличие от векторного механизма (push_back).

Если производительность является проблемой, вы можете попробовать unordered_set или что-то подобное.

Я просто хочу отметить, что начиная с C++11 и более поздних версий исходный код в вопросе компилируется отлично! Никаких ошибок. Тем не мение, vec.emplace_back() было бы лучше, так как он использует "новое размещение" внутри и поэтому более эффективен, копируя создание объекта прямо в память в конце вектора, а не имея дополнительную промежуточную копию.

cppreference состояния (курсив мой):

std::vector<T,Allocator>::emplace_back

Добавляет новый элемент в конец контейнера. Элемент построен через std::allocator_traits::construct, который обычно использует place -new для создания элемента на месте в месте, указанном контейнером.

Вот небольшая демонстрация, показывающая, что оба vec.push_back() и vec.emplace_back() теперь работает нормально.

Запустите его здесь: https://onlinegdb.com/BkFkja6ED.

       #include <cstdio>
#include <vector>

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    :   c(_c)
    {
        // Nothing here
    }

    // Copy constructor 
    A(const A& copy)
    : c(copy.c)
    {
        // Nothing here
    }    
};

int main(int argc, char *argv[])
{
    A foo(1337);
    A foo2(999);

    std::vector<A> vec;
    vec.push_back(foo); // works!
    vec.emplace_back(foo2); // also works!
    
    for (size_t i = 0; i < vec.size(); i++)
    {
        printf("vec[%lu].c = %i\n", i, vec[i].c);
    }
    
    return 0;
}

Вывод:

       vec[0].c = 1337
vec[1].c = 999

Обходной путь без const_cast,

A& operator=(const A& right) 
{ 
    if (this == &right) return *this; 
    this->~A();
    new (this) A(right);
    return *this; 
} 
Другие вопросы по тегам