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

РЕДАКТИРОВАТЬ: я узнал, что мой вопрос содержит парадокс. Я использовал расширенный список инициализаторов в своем коде, который был представлен в 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.

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[],

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);

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

Другие вопросы по тегам