Проблемы при написании конструктора копирования для умного указателя - PullRequest
3 голосов
/ 18 февраля 2009

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

Я обнаружил, что хотел написать код, подобный этому:

...
CountedPointer<Base> base;
...
CountedPointer<Derived> derived;
...
base = derived;

Однако конструктор копирования для CountedPointer имеет такой прототип, как этот:

CountedPointer(const CountedPointer<T> &other);

Таким образом, приведенный выше код не будет компилироваться, поскольку он не может найти подходящий конструктор (или оператор присваивания - там такая же история). Я попытался переписать конструктор копирования с помощью прототипа так:

template<U>
CountedPointer(const CountedPointer<U> &other);

Однако я столкнулся с проблемой, заключающейся в том, что конструктор копирования должен получить доступ к закрытому члену объекта, который он копирует (т. Е. Необработанный указатель), и если он имеет другую специализацию CountedPointer, они не видны.

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

Можно ли как-нибудь написать это, чтобы разрешить производную базовую копию, но не разрешить общий доступ к необработанному указателю?

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

Ответы [ 3 ]

4 голосов
/ 18 февраля 2009

Ты не можешь сделать их друзьями? Как:

template<typename T>
class CountedPointer {

    // ...

    template<U>
    CountedPointer(const CountedPointer<U> &other);

    template<typename U> friend class CountedPointer;
};
3 голосов
/ 18 февраля 2009

"Александреску избегает этой проблемы в своей библиотеке Loki, имея функции доступа для инкапсулированного указателя, но я бы предпочел не предоставлять прямой доступ к необработанному указателю, если это возможно"

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

Вы могли бы рассмотреть возможность создания таких отношений с базовым классом для всех CountedPointers. Возможно, вам придется поместить void * в этот базовый класс. Тогда вам придется выполнить всю проверку самостоятельно (если T извлечен из U, а затем вызвать приведение ... Могу ли я неявно преобразовать T в U ?, если это так, вызвать преобразование ... и т. Д.), Но это может оказаться довольно сложным , Вот грубое начало для этого подхода:

class CountedPointerBase
{
    void* rawPtr;
};

template <class T>
class CountedPointer : public ConutedPointerBase
{
      T* myRawT = reinterpret_cast<T*>(rawPtr);

      template<class U>
      CountedPointer( CountedPointer<U> u)
      {
           // Copying a CountedPointer<T> -> CountedPointer<U>
           if (dynamic_cast<U*>(myRawT) != NULL)
           {
               // Safe to copy one rawPtr to another
           }
           // check for all other conversions
      }
}

Может быть много других сложностей, чтобы увидеть, совместимы ли два типа. Возможно, есть некоторая гладкость шаблона Loki / Boost, которая может определить для двух аргументов типа, если вы можете привести один к другому.

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

0 голосов
/ 18 февраля 2009

Вы решите проблему, если у вас есть CountedPointer (T * другое); конструктор и аналогичный оператор присваивания.

...