Как вернуть закрытый указатель на список указателей как const? - PullRequest
0 голосов
/ 10 января 2019

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

Я не мог найти, как использовать reinterpret_cast или const_cast для этого.

class typeA{
    shared_ptr<list<shared_ptr<typeB>>> l;
public:
   shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};

Компилятор возвращает:

   error: could not convert ‘((typeA*)this)->typeA::x’ from ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<typeB> > >’ to ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<const typeB> > >’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

Кажется, что const shared_ptr<list<shared_ptr<typeB>>> и shared_ptr<const list<shared_ptr<typeB>>> работают нормально.

Можно ли сделать возврат l в виде полного константа, например:

const shared_ptr<const list<shared_ptr<const typeB>>>

или, по крайней мере, как:

shared_ptr<list<shared_ptr<const typeB>>> 

Ссылки вместо указателей не вариант. Объявление l как shared_ptr<list<shared_ptr<const typeB>>> также не является желаемым решением.

РЕДАКТИРОВАТЬ: нет 'int' больше.

Кажется, что не совсем то, что я хотел, но предложенные решения хороши. Да, копирование указателей допустимо.

Плохо, я не сразу набрал typeB. Я знаю о некоторых преимуществах ссылок над указателями, но я надеялся, что есть какое-то подобное решение.

Ответы [ 5 ]

0 голосов
/ 10 января 2019

То, что у вас есть, это ОЧЕНЬ сложная конструкция:

shared_ptr<list<shared_ptr<typeB>>> l;

Это три уровня косвенности, из которых два имеют управление временем жизни подсчета ссылок, а третий является контейнером (и при этом не является непрерывным для памяти).

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

shared_ptr<list<shared_ptr<const typeB>>>

Обратите внимание, что std::list<A> и std::list<const A> - это два разных типа по дизайну стандартной библиотеки. Когда вы хотите передать немодифицирующие дескрипторы своим контейнерам, вы обычно должны использовать const_iterator s.
В вашем случае есть shared_ptr поверх list, поэтому вы не можете использовать итераторы, если хотите использовать это поведение для подсчета ссылок.

В этот момент возникает вопрос: вы ДЕЙСТВИТЕЛЬНО хотите такое поведение?

  • Ожидаете ли вы ситуации, когда ваш экземпляр typeA будет уничтожен, но у вас все еще есть другие экземпляры typeA с тем же контейнером?
  • Ожидаете ли вы ситуации, когда все ваши typeA экземпляры, совместно использующие контейнер, будут уничтожены, но у вас все еще есть ссылки на этот контейнер в других местах вашей среды выполнения?
  • Ожидаете ли вы ситуации, когда сам контейнер будет уничтожен, но у вас все еще есть ссылки на некоторые элементы?
  • Есть ли у вас какая-либо причина использовать std::list вместо более обычных контейнеров для хранения общих указателей?

Если вы ответите ДА на все пункты маркера, то для достижения своей цели вам, вероятно, придется разработать новый класс, который будет вести себя как держатель для вашего shared_ptr<list<shared_ptr<typeB>>>, предоставляя только const доступ к элементам. ,

Если, однако, по одному из пунктов списка ваш ответ НЕТ, рассмотрите возможность изменения типа l. Я предлагаю начать с std::vector<typeB>, а затем только добавлять необходимые модификации одну за другой.

0 голосов
/ 10 января 2019

Проблема с шаблонами в том, что для любого

template <typename T>
class C { };

любые две пары C<TypeA> и C<TypeB> являются абсолютно не связанными классами & ndash; это даже в том случае, если TypeA и TypeB отличаются только по const -ness.

Так что то, что вы на самом деле хотите получить, технически невозможно. Я не буду сейчас представлять новый обходной путь, поскольку уже есть , но попробуйте посмотреть немного дальше: Как уже отмечалось в комментариях, вы могли бы столкнуться с проблемой XY.

Вопрос: что бы пользователь делал с таким списком? Возможно, он / она перебирает это - ndash; или получить доступ к отдельным элементам. Тогда почему бы не сделать так, чтобы весь ваш класс выглядел / вел себя как список?

class typeA
{
    // wondering pretty much why you need a shared pointer here at all!
    // (instead of directly aggregating the list)
    shared_ptr<list<shared_ptr<typeB>>> l;
public:
    shared_ptr<list<shared_ptr<typeB>>>::const_iterator begin() { return l->begin(); }
    shared_ptr<list<shared_ptr<typeB>>>::const_iterator end() { return l->end(); }
};

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

shared_ptr<typeB /* const or not? */> operator[](size_t index);

Теперь одна проблема все еще остается нерешенной: два возвращенных const_iterators имеют неизменный общий указатель, но pointee все еще изменчив!

Это небольшая проблема - вам нужно сейчас реализовать свой собственный класс итератора:

class TypeA
{
public:
    class iterator
    {
        std::list<std::shared_ptr<int>>::iterator i;         
        public:
        // implementation as needed: operators, type traits, etc.
    };
};

Посмотрите на std::iterator для полного примера & ndash; Имейте в виду, однако, что std::iterator устарела, поэтому вам нужно самостоятельно реализовать свойства типа.

Используемый тег итератора будет std::bidirectional_iterator_tag или random_access_iterator_tag (contiguous_iterator_tag с C ++ 20), если вы используете std::vector внутри.

Теперь важно, как вы реализуете два из необходимых операторов:

std::shared_ptr<int const> TypeA::iterator::operator*()
{
    return std::shared_ptr<int const>(*i);
}

std::shared_ptr<int const> TypeA::iterator::operator->()
{
    return *this;
}

Другие операторы просто передадут операцию внутренним итераторам (увеличение, уменьшение, если доступно, сравнение и т. Д.).

Я не утверждаю, что это Святой Грааль, путь, которым вы должны следовать при любых обстоятельствах. Но это ценная альтернатива, которую стоит хотя бы рассмотреть ...

0 голосов
/ 10 января 2019

Вы можете создать новый список const int из вашего исходного списка и вернуть, что:

std::shared_ptr<std::list<std::shared_ptr<const int>>> getList(){
    return std::make_shared<std::list<std::shared_ptr<const int>>>(l->begin(), l->end());
}

Если вы хотите запретить людям вносить изменения в возвращаемый список, сделайте его также постоянным:

std::shared_ptr<const std::list<std::shared_ptr<const T>>> getList(){
    return std::make_shared<const std::list<std::shared_ptr<const T>>>(l->cbegin(), l->cend());
}

Общий указатель, возвращаемый этой функцией, указывает не на исходный список, а на вновь созданный список.

Альтернативой может быть предоставление итераторов, которые при разыменовании возвращают const T& (где T - это тип, который вы фактически сохраняете). Таким образом, не нужно будет копировать весь список каждый раз, когда вы захотите пройти через него. Пример:

#include <iostream>
#include <list>
#include <memory>

struct example {
    int data;
    example(int x) : data(x) {}
};

template <class T>
class typeA {
    std::shared_ptr<std::list<std::shared_ptr<T>>> l = std::make_shared<std::list<std::shared_ptr<T>>>();
public:
    template< class... Args >
    void add( Args&&... args ) {
        l->emplace_back(std::make_shared<T>(std::forward<Args>(args)...));
    }

    // a very basic iterator that can be extended as needed   
    struct const_iterator {
        using uiterator = typename std::list<std::shared_ptr<T>>::const_iterator;
        uiterator lit;
        const_iterator(uiterator init) : lit(init) {}
        const_iterator& operator++() { ++lit; return *this; }
        const T& operator*() const { return *(*lit).get(); }
        bool operator!=(const const_iterator& rhs) const { return lit != rhs.lit; }
    };

    const_iterator cbegin() const noexcept { return const_iterator(l->cbegin()); }
    const_iterator cend() const noexcept { return const_iterator(l->cend()); }
    auto begin() const noexcept { return cbegin(); }
    auto end() const noexcept { return cend(); }
};

int main() {
    typeA<example> apa;
    apa.add(10);
    apa.add(20);
    apa.add(30);
    for(auto& a : apa) {
        // a.data = 5; // error: assignment of member ‘example::data’ in read-only object
        std::cout << a.data << "\n";
    }
}
0 голосов
/ 10 января 2019

Ваша проблема прямо лежит на

но я не хочу смешивать ссылки и указатели. Легче и чище иметь только указатели.

Здесь вы обнаружите, что это утверждение неверно . list<TypeB> может связать ссылку const list<TypeB> &, и ни один из членов списка не позволит модифицировать объекты TypeB.

class typeA {
    std::vector<typeB> l;
public:
    const std::vector<typeB> & getList() const { return l; };
};

Если вы действительно должны иметь const typeB, вы могли бы вместо этого вернуть проекцию из l, которая добавила const, но это не было бы Контейнер , но вместо него Range (при использовании библиотеки ranges, выбранной для C ++ 20, см. Также его автономный осуществление )

std::shared_ptr<const typeB> add_const(std::shared_ptr<typeB> ptr)
{
    return { ptr, ptr.get() };
}

class typeA {
    std::vector<std::shared_ptr<typeB>> l;
public:
    auto getList() const { return l | std::ranges::transform(add_const); };
};

Другой альтернативой является то, что вы можете обернуть ваши std::shared_ptr в что-то вроде std::experimental::propagate_const и просто вернуть их прямо.

0 голосов
/ 10 января 2019

Когда вы конвертируете указатель в nonconst в указатель в const, у вас есть два указателя. Кроме того, список указателей на неконстантный тип полностью отличается от списка указателей на констант.

Таким образом, если вы хотите вернуть указатель на список pointers-to-const, вы должны иметь список pointers-to-const. Но у вас нет такого списка. У вас есть список указателей на nonconst, и эти типы списков не могут быть преобразованы.

Конечно, вы можете преобразовать ваши указатели на неконстантные в список указателей на константы, но вы должны понимать, что это отдельный список. Указатель на первый тип не может указывать на последний.

Итак, вот пример для преобразования списка (я не проверял, может содержать опечатки или ошибки):

list<shared_ptr<const int>> const_copy_of_list;
std::transform(l->begin(), l->end(), std::back_inserter(const_copy_of_list),
               [](auto& ptr) {
    return static_pointer_cast<const int>(ptr);
});
// or more simply as shown by Ted:
list<shared_ptr<const int>> const_copy_of_list(l->begin(), l->end());

Поскольку мы создали совершенно новый список, на который нельзя указать l, возвращать указатель бессмысленно. Вернем сам список. Вызывающий может обернуть список в совместное владение, если это необходимо, но не обязан, когда это соответствует их потребностям:

 list<shared_ptr<const int>> getConstCopyList() {
      // ... the transorm above
      return const_copy_of_list;
 }

Обратите внимание, что, хотя список является отдельным, указатели внутри все еще указывают на те же самые целые числа.


В качестве примечания, подумайте, имеет ли смысл совместное владение объектом int для вашей программы - я предполагаю, что это упрощение для примера.

Также пересмотрите, является ли разумным требованием «Ссылки вместо указателей».

...