Использование собственного шаблонного класса owner_ptr для размещения многомерного массива

EDIT: я обнаружил, что мой вопрос содержит парадокс. В своем коде я использовал расширенный список инициализаторов, который появился в C++11, но я хотел использовать только инструменты C++98. Извините, я слишком поздно заметил предупреждающее сообщение моего компилятора. Конечно, семантика перемещения решила бы мою проблему в C++11. Мой вопрос больше не имеет значения.

Я решил создать собственный класс owner_ptr. Концепция заключается в том, что объект owner_ptr имеет право собственности на динамически выделяемый объект (или объекты). Один и тот же объект не должен принадлежать более чем 1 owner_ptr. Вот моя реализация:

#include <cstddef>

template <typename T>
class owner_ptr
{
    T* ptr;
    bool array;

public:
    owner_ptr() : ptr(NULL) {} /* LINE 10 */
    owner_ptr(T* ptr, bool isArray = false) : ptr(ptr), array(isArray) {} /* LINE 11 */
    owner_ptr(owner_ptr<T>& orig) : ptr(orig.ptr), array(orig.array) /* LINE 12 */
    {
        orig.ptr = NULL;
    }

    ~owner_ptr()
    {
        if (ptr != NULL)
        {
            if (!array)
            {
                delete ptr;
            }
            else
            {
                delete[] ptr;
            }
        }
    }

    owner_ptr& operator=(owner_ptr<T>& rvalue)
    {
        if (this != &rvalue)
        {
            this->~owner_ptr();
            ptr = rvalue.ptr;
            array = rvalue.array;
            rvalue.ptr = NULL;
        }
        return *this;
    }

    void reset()
    {
        this->~owner_ptr();
        ptr = NULL;
    }

    void addPtr(T* newPtr, bool isArray = false)
    {
        this->~owner_ptr();
        ptr = newPtr;
        array = isArray;
    }

    T& operator*() { return *ptr; }
    const T& operator*() const { return *ptr; }

    T* get() { return ptr; }
    const T* get() const { return ptr; }

    T* operator->() { return ptr; }
    const T* operator->() const { return ptr; }

    T& operator[](int i) { return ptr[i]; }
    const T& operator[](int i) const { return ptr[i]; }
};

Я указал, что ответственность за создание допустимых owner_ptr объектов, таких как:

owner_ptr<int> op1(new int);
owner_ptr<int> op2(new int[3], true);
owner_ptr<int> op3(op1);
owner_ptr<int> op4;
op4 = op3;

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

int main()
{
    owner_ptr< owner_ptr<int> > test(new owner_ptr<int>[2]{ owner_ptr<int>(new int[5], true), owner_ptr<int>(new int[8], true) }, true); /* LINE 72 */

    return 0;
}

Я получил следующие сообщения:

\main.cpp|72|error: no matching function for call to 'owner_ptr<int>::owner_ptr(owner_ptr<int>)'|
\main.cpp|72|note: candidates are:|
\main.cpp|12|note: owner_ptr<T>::owner_ptr(owner_ptr<T>&) [with T = int]|
\main.cpp|12|note:   no known conversion for argument 1 from 'owner_ptr<int>' to 'owner_ptr<int>&'|
\main.cpp|11|note: owner_ptr<T>::owner_ptr(T*, bool) [with T = int]|
\main.cpp|11|note:   no known conversion for argument 1 from 'owner_ptr<int>' to 'int*'|
\main.cpp|10|note: owner_ptr<T>::owner_ptr() [with T = int]|
\main.cpp|10|note:   candidate expects 0 arguments, 1 provided|
\main.cpp|72|error: no matching function for call to 'owner_ptr<int>::owner_ptr(owner_ptr<int>)'|
\main.cpp|72|note: candidates are:|
\main.cpp|12|note: owner_ptr<T>::owner_ptr(owner_ptr<T>&) [with T = int]|
\main.cpp|12|note:   no known conversion for argument 1 from 'owner_ptr<int>' to 'owner_ptr<int>&'|
\main.cpp|11|note: owner_ptr<T>::owner_ptr(T*, bool) [with T = int]|
\main.cpp|11|note:   no known conversion for argument 1 from 'owner_ptr<int>' to 'int*'|
\main.cpp|10|note: owner_ptr<T>::owner_ptr() [with T = int]|
\main.cpp|10|note:   candidate expects 0 arguments, 1 provided|

Это так странно. У меня есть следующие вопросы:

  • Почему компилятор ищет функцию owner_ptr<int>::owner_ptr(owner_ptr<int>)? Такой конструктор-копия не имел бы смысла.
  • Почему нельзя преобразовать 'owner_ptr<int>' в 'owner_ptr<int>&'?
  • Как я могу это исправить? Можно ли выделить многомерное (используя шаблон owner_ptr) только одну команду?

Я знаю, что это работает:

owner_ptr< owner_ptr<int> > test(new owner_ptr<int>[2], true);
test[0].addPtr(new int[5], true);
test[1].addPtr(new int[8], true);

Однако мне любопытно, можно ли это сделать одной командой.

ПРИМЕЧАНИЕ. Я делаю это в учебных целях. Это не производственный код. Поэтому, пожалуйста, не рекомендуйте мне использовать интеллектуальные указатели C++11.


person Gergely Tomcsányi    schedule 12.08.2017    source источник
comment
Я не собираюсь предлагать использовать умные указатели C++11, но я предлагаю изучить их, по крайней мере, в их интерфейсе. isArray параметр зверство для начала.   -  person Revolver_Ocelot    schedule 12.08.2017
comment
Кроме того, в отношении второго пункта: stackoverflow.com/questions/1565600/   -  person Revolver_Ocelot    schedule 12.08.2017
comment
Для целей обучения нет причин делать это с С++ 98...   -  person Phil1970    schedule 12.08.2017


Ответы (2)


Я указал, что ответственность за создание законных объектов owner_ptr лежит на пользователе, например:

owner_ptr<int> op1(new int);
owner_ptr<int> op2(new int[3], true);
owner_ptr<int> op3(op1);
owner_ptr<int> op4;
op4 = op3;

Это довольно ужасная идея. В идеале ваш класс owner_ptr должен автоматически выводить это из типа, если у вас есть массив T или T[].

IIRC Я сделал это для некоторого производственного кода, просто переписав полную специализацию std::auto_ptr<T> как std::auto_array<T[]> и сделав это кристально понятным для пользователя.
Это очень похоже на вашу попытку owner_ptr<T> исправить правильные new и delete вызовы для принятия owner_ptr<T[]>.

С применением ограничений C++98 (без поддержки type_traits) это кажется самым простым способом. Вам просто нужно заменить коды, где new должно быть new[], а delete должно быть delete[].

person user0042    schedule 12.08.2017
comment
Извините, но я не совсем понимаю, как это отвечает на мой вопрос. - person Gergely Tomcsányi; 12.08.2017
comment
@GergelyTomcsányi Вы сделали тривиальную ошибку: new owner_ptr<int>[2] вы имели в виду new owner_ptr<int[2]>. - person user0042; 12.08.2017
comment
Ты неправ. owner_ptr< owner_ptr<int> > хранит owner_ptr<int> объектов, а не owner_ptr<int*> объектов, поэтому owner_ptr<int[2]> неверно. - person Gergely Tomcsányi; 13.08.2017
comment
В любом случае, мой вопрос больше не имеет значения. Это расширенный список инициализаторов, вызвавший проблему, который является инструментом С++ 11. Я узнал об этом слишком поздно, потому что хочу использовать в своем коде только инструменты C++98 (пока). Ссылка Rvalue, семантика перемещения и конструктор перемещения легко решили бы мою проблему. - person Gergely Tomcsányi; 13.08.2017
comment
Расширенный список-инициализаторов хочет вызвать конструктор-копию (о котором я сначала не знал), но у меня есть только конструктор-копия owner_ptr<int>(owner_ptr<int>&). Проблема в том, что я не могу передать временные объекты этой функции по неконстантной ссылке (передача по owner_ptr<int>&& будет работать, но это инструмент С++ 11). Поэтому я удалю список инициализаторов и использую метод, который я упомянул в своем вопросе, и он сработал. - person Gergely Tomcsányi; 13.08.2017
comment
Ты сказал, моя идея ужасна. Это правда, но это не производственный код. Тем не менее, вы упомянули идею, которая мне кажется интересной, но мне непонятно одно. Как я могу специализировать шаблон на тип owner_ptr<T[]>? Размер массива известен во время выполнения. - person Gergely Tomcsányi; 13.08.2017
comment
@GergelyTomcsányi На самом деле я не специализировался на этом, а написал другую версию с другим именем. - person user0042; 13.08.2017

owner_ptr(T* ptr, bool isArray = false)
               // ^^^^^^^^^^^^^^^^^^^^

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

template<typename T>
class owner_ptr {
    static void DefaultDeleter(T* p) {
        delete p;
    }
public:
    typedef void(*DeleterFunc)(T*);

    static void ArrayDeleter(T* p) {
        delete[] p;
    }
    owner_ptr(T* ptr, DeleterFunc = DefaultDeleter);
};

Это можно использовать тогда, как

owner_ptr<int> op1(new int());
owner_ptr<int> op2(new int[10],owner_ptr<int>::ArrayDeleter);

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

person user0042    schedule 13.08.2017