Сохранение const * в константном векторе - PullRequest
3 голосов
/ 22 октября 2019

Соответствующий интерфейсный код, с которым мне приходится иметь дело, состоит из двух функций: одна для извлечения объектов, а другая - для отправки объектов в виде векторов. Проблема в том, что функция поиска возвращает const Object*, но функция submit ожидает const vector<Object*>.

Я знаю, что это можно решить с помощью const_cast<Object*>, но есть ли другой, более чистый способ?

Вот код, который демонстрирует проблему:

#include <vector>

//////////// REPRESENTATIVE INTERFACE IMPLEMENTATION, DO NOT TOUCH ///////
struct Object{};

class Interface{
    public:
    size_t getNumObjects() const {return 10;}
    const Object* getObject(size_t index) const {return nullptr;}
};
const Interface interface;

void submitObjects(const std::vector<Object*> &objects);
//////////////////////////////////////////////////////////////////////

// Task: take all objects from 'interface' and submit them to 'submitObjects'.

int main(){

    std::vector<const Object*> objects;
    for(size_t i = 0; i < interface.getNumObjects(); i++){
        const Object* object = interface.getObject(i);
        objects.push_back(object);
    }

    submitObjects(objects); // ERROR: no known conversion from 'vector<const Object *>' to 'const vector<Object *>'

    return 0;
}

Единственный способ, которым я мог придумать, чтобы решить эту проблему, это сделать objects a std::vector<Object*> и вставить объекты с objects.push_back(const_cast<Object*>(object));, но я чувствую, что должно быть лучшее решение.

Любые идеи приветствуются.


Еще немного справочной информации, почему эта проблема является действительной: const vector<Object*> может толькопоставить const Object*, как видно из всех его перегрузок геттера, как видно здесь: https://de.cppreference.com/w/cpp/container/vector/operator_at

Следовательно, vector<const Object*> почти никогда не используется в интерфейсах. Поэтому идея объединения большого количества const Object* в один большой const vector<Object*> является верной концепцией, но кажется невозможной без const_cast.

EDIT : Указанное выше неверно, спасибо за разъяснения. const std::vector<Object*> может создавать неконстантные указатели, потому что мое понимание константности указателя было неверным.

Ответы [ 3 ]

7 голосов
/ 22 октября 2019

Вы неправильно понимаете документацию / правила набора текста. Если у вас T = Object*, то const T - это не const Object*, а Object* const. Вот как работают C и C ++.

Поэтому ваше утверждение, что «вы не можете получить изменяемый объект из const vector<Object*>, неверно. Чего вы не можете сделать, так это изменить вектор или значения элементов (то есть изменитьуказатели). Но методы доступа возвращают ссылки на Object* const, поэтому все еще могут изменять указанные объекты:

void foo(const std::vector<int*>& v)
{
    (*v[0])++; // Increments the integer pointed to by the first vector element.
}

https://godbolt.org/z/HGIb20

Это просто из-за того, что const неБыть транзитивным. Вот еще один пример:

struct X
{
    int* y;
};

void foo(const X& x)
{
    (*x.y)++;
}

https://godbolt.org/z/NmCwGt


Учитывая это, интерфейс, который у вас есть, не позволяет вам делать то, что вы хотите. Вы только вручаете указателинеизменяемым объектам, но предполагается, что они передают указатели на изменяемые объекты (функция submitObjects только обещает не изменять контейнер, но оставляет за собой право изменять объекты). Это просто невозможно.

const_cast может быть допустимым параметром , если (и это очень важно, если!), Вы на 100% знаете, что объекты на самом деле не объявлены как const, где бы они ни создавались. это оба дне имеет смысла интерфейса и является чрезвычайно хрупким, хакерским подходом, который вы даже не должны учитывать в этих обстоятельствах.

5 голосов
/ 22 октября 2019

Ваш интерфейс возвращает указатель на постоянный объект. Вы хотите отправить константный объект в функцию, которая требует изменяемых объектов.

Это не просто неопределенное поведение, это сюрпризы.

Представьте, что тот, кто реализует интерфейс, хранит вычисленные значения ообъекты и помечается как грязный при вызове потенциально мутирующего метода. Там была бы веская причина действительно возвращать указатель на постоянные объекты. Изменение объекта const приведет к нарушению инварианта класса.

Если вы отправите эту константу в submitObjects, и ваш объект там будет мутирован, то вы в некотором смысле солгали: интерфейс посылает вам константные объекты, и вы мутируете их. Вы нарушили контракт.

Если submitObjects не изменяет свои аргументы, то нет причин сохранять изменяемость аргумента.


Что касается путаницы с const, вотпример константного вектора указателя на изменяемые данные:

// const std::vector<int*>
auto const cannot_erase_or_add = std::vector<int*>{
    new int{1},
    new int{2},
    new int{3}
};

// Error!
// cannot_erase_or_add.push_back(new int{4});

// Error!
// cannot_erase_or_add.clear();

// operator[] return int*, because it's a vector of int*
int* one = cannot_erase_or_add[0];

// totally legal, int*, no const there
*one = 2;

С другой стороны, здесь все наоборот:

// std::vector<int const*>
auto can_erase_or_add = std::vector<int const*>{
    new int{1},
    new int{2},
    new int{3}
};

// Works
can_erase_or_add.clear();

// Works
can_erase_or_add.push_back(new int{4});

// operator[] return int const*, because it's a vector of int const*
int const* four = can_erase_or_add[0];

// Can't do that, pointer to const
// *four = 2;
4 голосов
/ 22 октября 2019

Возможны следующие варианты:

  1. Изменить submitObjects для принятия std::vector<const Object*>.
  2. Или изменить Interface::getObject для возврата указателя на неконстантный Object.
  3. Или используйте const_cast, как вы выяснили. Избегайте этого, если можете.

Если submitObjects изменяет указанные объекты, то 3. опасно, если указанные объекты не следует изменять (например, если они на самом деле являются постоянными объектами). Если все в порядке для изменяемых объектов, выберите 2.

Если submitObjects не изменяет заостренные объекты, тогда не должно быть проблем с выполнением изменения 1.

Если submitObjects изменяет указанные объекты, и объекты не могут быть изменены, то у вас есть логическое противоречие, и нет другого решения, кроме как не передавать указатели на функцию.

невозможно извлечь изменяемые указатели из const vector<Object*>?

Только указатели нельзя изменять. Но вы можете мутировать заостренные предметы. Пример:

struct Object{ int member; };
Object o;
const vector<Object*> objects{&o};
objects[0]->member = 42; // well-defined

Я думал о 1., но это означало бы, что он больше не будет принимать std :: vector, требуя еще одну перегрузку

ИлиВы можете использовать шаблон. В частности, это потенциально может быть хорошим вариантом использования для итераторов ввода или диапазонов.

...