1

EDIT: I've found out that my question contains a paradox. I used extended initializer list in my code, which was introduced in C++11, but I wanted to use only C++98 tools. Sorry, I noticed my compiler's warning message too late. Of course, move-semantics would have solved my problem in C++11. My question doesn't matter anymore.

I decided to create an own owner_ptr class. The concept is that an owner_ptr object has ownership over a dynamically allocated object (or objects). More than 1 owner_ptr mustn't own the same object. Here is my implementation:

#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]; }
};

I specified that it's the user's responsibility to create legal owner_ptr objects like:

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;

It works well with one-dimensional arrays. However, when I try to allocate a two-dimensional array, this code doesn't compile:

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

I got the following messages:

\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|

This is so weird. I have the following questions:

  • Why is the compiler looking for a owner_ptr<int>::owner_ptr(owner_ptr<int>) function? Such a copy-constructor would make no sense.
  • Why isn't it possible to convert from 'owner_ptr<int>' to 'owner_ptr<int>&'?
  • How can I fix it? Is it possible to allocate a multi-dimensional (using the owner_ptr template) with only one command?

I know that this works:

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

However, I'm curious, is it possible to do it with one command.

NOTE: I'm doing this for learning purposes. This is not a production code. So please, don't recommend me to use the C++11 smart pointers.

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • I'm not going to suggest to use C++11 smart poiters, but I am going to suggest to look into them, at least at their interface. `isArray` parameter is atrocity for starters. – Revolver_Ocelot Aug 12 '17 at 14:58
  • 1
    Also, related to second bullet: https://stackoverflow.com/questions/1565600/how-come-a-non-const-reference-cannot-bind-to-a-temporary-object?rq=1 – Revolver_Ocelot Aug 12 '17 at 14:58
  • For learning purpose, there are no reason to do it with C++98... – Phil1970 Aug 12 '17 at 15:34

2 Answers2

0

I specified that it's the user's responsibility to create legal owner_ptr objects like:

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;

That's a pretty awful idea. Ideally your owner_ptr class should be able to deduce that from the type automatically if you have a T or a T[] array.

IIRC I did so for some production code, by simply rewriting a complete specialization of std::auto_ptr<T> as std::auto_array<T[]> and make that crystal clear for the user.
That's very similar to your attempt of making a owner_ptr<T> fixing the right new and delete calls for accepting a owner_ptr<T[]>.

With C++98 restrictions applied (no type_traits support), this seems to be the most straightforward way. You'll just have to replace the codes where new needs to be new[] and delete needs to be delete[].

user0042
  • 7,917
  • 3
  • 24
  • 39
  • Sorry, but I don't clearly understand how this answers my question. – Gergely Tomcsányi Aug 12 '17 at 15:12
  • @GergelyTomcsányi You made a trivial error: `new owner_ptr[2]` what you meant was `new owner_ptr`. – user0042 Aug 12 '17 at 15:30
  • You are wrong. `owner_ptr< owner_ptr >` stores `owner_ptr` objects, not `owner_ptr` objects, so `owner_ptr` is wrong. – Gergely Tomcsányi Aug 12 '17 at 21:55
  • Anyway, my question doesn't matter anymore. It is the extended initializer-list which caused the problem, which is a C++11 tool. I've found out it too late, because I want to use only C++98 tools in my code (for now). Rvalue reference, move-semantics and move-constructor would have easily solved my problem. – Gergely Tomcsányi Aug 12 '17 at 23:42
  • The extended initializer-list wants to call the copy-constructor (which I didn't know at first), but I only have an `owner_ptr(owner_ptr&)` copy-constructor. The problem is, that I can't pass temporary objects to this function by non-const reference (passing by `owner_ptr&&` would work, but it's a C++11 tool). So I will remove the initializer-list and use the method which I mentioned in my question, and worked. – Gergely Tomcsányi Aug 12 '17 at 23:43
  • You said, my idea is awful. This is true, but it's not a production code. However, you have mentioned an idea, which I find interesting, but one thing is not clear to me. How can I specialize the template to an `owner_ptr` type? The size of the array is known at runtime. – Gergely Tomcsányi Aug 12 '17 at 23:46
  • @GergelyTomcsányi I didn't actually specialize it, but wrote another version with a different name. – user0042 Aug 13 '17 at 00:15
0
owner_ptr(T* ptr, bool isArray = false)
               // ^^^^^^^^^^^^^^^^^^^^

A generally better and more flexible solution than passing an indicator for pointer or array types would be to let the user provide a deleter function in the constructor:

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

That can be used then like

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

and gives more flexibility about any custom deleter functions that should be called.

user0042
  • 7,917
  • 3
  • 24
  • 39