LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   lvalue operator / consumable objects (http://www.linuxquestions.org/questions/programming-9/lvalue-operator-consumable-objects-545746/)

ta0kira 04-13-2007 09:32 AM

lvalue operator / consumable objects
 
Take the following bit of code. My first question is do explicit constructors prohibit using = in construction? GCC accepts this code, but Comeau online seems to want to construct the 'Consumable's by first creating an rvalue-'Consumable'. Does the C++ standard require () instead of = in this case?
Code:

#include <iostream>


class Consumable
{
public:
    explicit Consumable(int *pPointer) :
        Pointer(pPointer) {}

    Consumable(Consumable &eEqual) :
        Pointer(eEqual.Pointer)
    { eEqual.Pointer = 0; }

    operator Consumable&() { return *this; }

    int Value() const { return Pointer? *Pointer : 0; }

    ~Consumable() { delete Pointer; }

private:
    explicit Consumable(const Consumable&) {}
    Consumable &operator = (const Consumable&) { return *this; }

    int *Pointer;
};


Consumable Transfer(Consumable oOld)
{ return oOld; }


int main()
{
    int *Value = new int(100);

    Consumable Test1 = Value;
    std::cout << "Test1: " << Test1.Value() << "\n";

    Consumable Test2 = Test1;
    std::cout << "Test2: " << Test2.Value() << "\n";
    std::cout << "Test1: " << Test1.Value() << "\n";

    Consumable Test3 = Transfer(Test2);
    std::cout << "Test3: " << Test3.Value() << "\n";
    std::cout << "Test2: " << Test2.Value() << "\n";
    std::cout << "Test1: " << Test1.Value() << "\n";
}

My second question is what is your opinion of an object which is consumed by a function it is used in? In a library I'm building I use the pattern above using single-use objects that are consumed when passed to a function. This is a memory management measure: certain functions take/return a consumable object to allow the user to modify the structure of dynamic objects. The idea of the object is that a user can store one temporarily while they figure out where to put it, but if their temp goes out of scope then the enclosed pointer is deleted if the object hasn't been consumed by another function. In other words, a function returns a pointer to the user under the condition they return it to something "responsible". If they don't then that pointer will be deleted, and if they don't even store the return then it is destroyed at the end of the call. Thanks.
ta0kira

PS I am not using auto_ptr for these reasons:
1) basic lib will have no STL or explicit libc ties
2) cannot have an assignment operator due to ambiguity
3) only certain objects may release the pointer
4) the "don't delete me" criteria is type-specific

dmail 04-13-2007 10:02 AM

I would say g++ should not be allowing this to compile what compiler warning level are you using? also looks like you are reinventing a scoped pointer class.

[edit]
Here is copy of the scoped shared pointer class I use, you would need to adjust it if you wanted to use it tho as the release function releases it reference to the pointer not releases the actual pointer from the container to the user.

Code:

///////////////////////////////////////////////////////////////////////////////
///  shared_ptr.h
///  A very simple implementation of a shared scope pointer.
///  On going out of scope if decrements the counter so you must store it if
///  you want to use it later/
///  @remarks
///  It should look and feel like a normal pointer if you see anything
///  that does not seem correct, please let me know.
///  Warning!!!!!!!!!!!!!!!! 
///  do not delete/null the pointer returned by ptr() you do not own it.
///  We could make it a T const* but this may cause problems????????????????
///  Added a release which will decrement the counter and delete if its zero.
///  @author Liam Devine @date 17/02/2007
///  update:
///  added static and dynamic casting
///////////////////////////////////////////////////////////////////////////////
#ifndef SHARED_PTR_H_
#        define SHARED_PTR_H_
#        include <cassert>
#        include <stdexcept>

namespace LDR
{
        //removed the ref counter to outside of the shared_ptr scope as
        //when casting the raw underlying pointer it would require another cast
        struct Counter
        {
                Counter():m_amount(0){}
                int m_amount;
        };

        //forward declarations
        template<typename T>class Shared_ptr;
        template<typename TO,typename FROM>Shared_ptr<TO> sp_dynamic_cast(FROM rhs);
        template<typename TO,typename FROM>Shared_ptr<TO> sp_static_cast(FROM rhs);

        template<typename T>
        class Shared_ptr
        {
        public:
                //struct Counter
                //{
                //        Counter():m_amount(0){}
                //        int m_amount;
                //};
        public:
                Shared_ptr():m_ptr(0),m_count(0){}

                Shared_ptr(T* pointer):m_ptr(pointer),m_count(new Counter)
                {
                        assert(m_ptr && m_count);
                        ++m_count->m_amount;
                }
                Shared_ptr(T* pointer, Counter* c):m_ptr(pointer),m_count(c)
                {
                        assert(m_ptr && m_count);
                        ++m_count->m_amount;
                }
                Shared_ptr(Shared_ptr<T> const& rhs):m_ptr(rhs.m_ptr),m_count(rhs.m_count)
                {
                        assert(rhs.m_ptr && rhs.m_count);
                        ++m_count->m_amount;
                }
                Shared_ptr<T>& operator = (Shared_ptr<T> const& rhs)
                {
                        assert(rhs.m_ptr && rhs.m_count);
                        m_ptr = rhs.m_ptr;
                        m_count = rhs.m_count;
                        ++m_count->m_amount;
                        return *this;
                }
                ~Shared_ptr()
                {
                        if(m_count)
                        {
                                if(m_count->m_amount>0)
                                {
                                        --m_count->m_amount;
                                        if(m_count->m_amount==0)
                                        {
                                                delete m_ptr;m_ptr=0;
                                                delete m_count;m_count=0;
                                        }
                                }
                        }
                }
                T* operator->()const
                {
                        return m_ptr;
                }
                T& operator*()const
                {
                        return *m_ptr;
                }
                T* ptr()const
                {
                        return m_ptr;
                }
                T& ref()const
                {
                        return *m_ptr;
                }
                bool operator ==(Shared_ptr<T> const& rhs)const
                {
                        return m_ptr == rhs.m_ptr;
                }
                bool operator !=(Shared_ptr<T> const& rhs)const
                {
                        return ! (m_ptr == rhs.m_ptr);
                }
                operator bool ()const
                {
                        return m_ptr !=0;//&& m_count && m_count->m_amount != 0;
                }
                int count()const
                {
                        return m_count ? m_count->m_amount : 0;
                }
                void release()
                {
                        if(m_count)
                        {
                                if(m_count->m_amount > 0)
                                {
                                        --m_count->m_amount;
                                }
                                else
                                {
                                        delete m_count;
                                        if(m_ptr)delete m_ptr;
                                }
                                m_count=0;
                                m_ptr=0;
                        }
                }
        private:
                //casting friends
                template<typename TO,typename FROM>friend Shared_ptr<TO> sp_dynamic_cast(FROM);
                template<typename TO,typename FROM>friend Shared_ptr<TO> sp_static_cast(FROM);
                T* m_ptr;
                Counter* m_count;
        };


        template<typename TO,typename FROM>
                Shared_ptr<TO> sp_static_cast(FROM rhs)
        {       
                Shared_ptr<TO> r ( static_cast<TO*>( rhs.m_ptr),rhs.m_count );
                return r;
        }


        template<typename TO,typename FROM>
                Shared_ptr<TO> sp_dynamic_cast(FROM rhs)
        {
                TO* p = dynamic_cast<TO*>(rhs.m_ptr);
                if(!p){ throw std::runtime_error("dynamic_cast error"); }
                return Shared_ptr<TO> (p,rhs.m_count);
        };

}


#endif//SHARED_PTR_H_


ta0kira 04-13-2007 10:28 AM

I am using -Wall and don't get any warnings (I rewrote this from memory but it's essentially the same.) I did find an answer for the first question: 'explicit' does require (), however I think the line 'Consumable Test3 = Transfer(Test2);' should implicitly call 'operator Consumable&()' (with GCC it does.)

I do need a custom class in this case because deletion is conditional based on the result of a virtual function called on the pointer. What I'd really like to know is if it's too non-standard that a 'Consumable' passed to a function is no longer usable. Again, I don't want any explicit external dependencies. Thanks.
ta0kira

PS A scoped pointer can't be copied, however I need mutating copying in the way auto_ptr has and no assignment operator (whether default or mutating.) A shared pointer is too much overhead and the reference counting is redundant in the case of my lib.

dmail 04-13-2007 10:51 AM

This is the output g++ gives me
Quote:

$ g++ taokira.cpp -Wall -ansi -pedantic -o taokira
taokira.cpp: In function `int main()':
taokira.cpp:36: error: conversion from `int*' to non-scalar type `Consumable' re
quested

ta0kira 04-13-2007 10:58 AM

Yes, I guess I mis-typed when rewriting that part (or maybe it's the -ansi that got me.) If you change that line to 'Consumable Test1(Value);' then it should work. My other questions/comments still stand, however. Thanks.
ta0kira

PS I am without GCC right now.

dmail 04-13-2007 11:08 AM

That was my reasoning for thinking it should not compile:)

Quote:

My second question is what is your opinion of an object which is consumed by a function it is used in?
You mean just like an auto_ptr ?
Quote:

In a library I'm building I use the pattern above using single-use objects that are consumed when passed to a function. This is a memory management measure: certain functions take/return a consumable object to allow the user to modify the structure of dynamic objects.
Consumable object have the requirement that they need to be passed by reference then yes?
Quote:

The idea of the object is that a user can store one temporarily while they figure out where to put it, but if their temp goes out of scope then the enclosed pointer is deleted if the object hasn't been consumed by another function.
Again just like an auto pointer

Quote:

In other words, a function returns a pointer to the user under the condition they return it to something "responsible". If they don't then that pointer will be deleted, and if they don't even store the return then it is destroyed at the end of the call. Thanks.
Yes I can understand why you don't want to use an auto_ptr (think maybe because of differing STL versions?) but it provides all the functionality you are after. So maybe you should try emulating the same sort of behaviour that auto_ptr has (similar to the code I shown), It's a wrapper for a pointer so there should be a bool operator, pointer operator etc so to the outside world it looks and feels just like a pointer and allows a user to typedef it and never have to worry about how different it is to a pointer.

ta0kira 04-13-2007 01:23 PM

Quote:

Originally Posted by dmail
Yes I can understand why you don't want to use an auto_ptr (think maybe because of differing STL versions?) but it provides all the functionality you are after. So maybe you should try emulating the same sort of behaviour that auto_ptr has (similar to the code I shown), It's a wrapper for a pointer so there should be a bool operator, pointer operator etc so to the outside world it looks and feels just like a pointer and allows a user to typedef it and never have to worry about how different it is to a pointer.

I've already got the bool, !, ->, and * implemented. It intentionally won't be transparent except for those functions, however (i.e. conversion-to-pointer operator and * return const) because they aren't supposed to use it as a pointer; just hold on to it long enough to place it somewhere else. The smart pointer will hold a node removed from a tree and allow the user to hang on to it while deciding where to reinsert it and is by no means meant to give them access to a real pointer (appropriate functions return a real pointer.) I'm going completely non-template here so I need to use an lvalue operator instead of returning a proxy class like auto_ptr. The main reason for not using auto_ptr is the assignment operators, its templated constructors, and a check I need to make in the destructor before deleting the pointer.
Quote:

Originally Posted by dmail
Consumable object have the requirement that they need to be passed by reference then yes?

The mutating copy constructor handles that (but requires the lvalue conversion operator when embedding function calls), but I'm thinking about changing to references just for clarity. That will cut down on a few copies, but isn't required.
ta0kira

ta0kira 10-22-2008 11:28 PM

It turns out the lvalue operator isn't actually used by stricter compilers (such as gcc 4.2.4 in Slackware 12.1-current,) so I just converted to a reference counter, anyway! It made me remember this discussion. The public interface only allows access to the const pointer and a friend class has access to the non-const version so that it can store it. The const-only public access prevents the developer from accidentally using the same pointer with two separate groups of reference-counting pointers. Before I was afraid that a smart pointer would go out of scope too soon, but it isn't a problem with the current library version. Thanks for the help, although very late!
ta0kira

ta0kira 10-23-2008 10:27 PM

Nevermind! It wasn't quite that simple. In multi-threaded applications, it's possible for a reference counter to hold a pointer to an object and have the object be deleted independently. Sort of just talking to myself, but I'm just giving an update.
ta0kira

The solution seems to be reference-counting smart pointers plus linked lists of the smart pointers. It's all very complex, but I guess that's the price of memory management!


All times are GMT -5. The time now is 07:32 PM.