Thread: Ban pointers or references on classes?

  1. #46
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by brewbuck View Post
    I can imagine no way to prevent creation of references in C++. To prevent creation of instances, use a private constructor.
    I'm talking about creating new instances on the heap, not on the stack. Making the constructor private will make it impossible to create the object at all.

    If the language makes it so difficult to achieve X, perhaps achieving X is not a good idea.
    Perhaps. If there's no easy, achievable way, it's not the end of the world.

  2. #47
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Quote Originally Posted by Elysia View Post
    I can't see how that works. The ownership of the memory belongs to NONE but the class itself. Even if a object goes out of scope, the memory won't be freed until the ref count is 0.
    And I also do not believe that having "special exception" functions is a good idea. From the sound of it, I need a function like PassByThread() to increase ref count or create another copy of the variable.

    But regardless... if just fun fun's sake, is it possible to ban references and ALL creation of new instances of it on the heap?
    Unfortunately there are a number of things that simply become necessary when dealing with anything that is shared amoung threads. For example, altering the refcount of the smart pointer can no longer be done with a simple refCount++. You have to use InterlockedIncrement, a CriticalSection, or something like that. You also start needing to use the 'volatile' keyword.
    For refcounting you perhaps already need to use the 'mutable' keyword if you write const-correct code.

    Yes if you've made AddRef and Release (or whatever you called them) private, which is fine, then you would quite possibly need some other way of dealing with passing to a thread. You'll find that most smart pointer classes allow some way of transferring ownerhip through calls to or from functions that can't take a smart pointer, such as by having a Attach and Detach functions. External functions such as CreateThread that do not let you pass the type of object you want by value are precisely when you need such things. Using Attach and Detach instead of direct calls to the objects AddRef and Release methods tends to mean cleaner and exception-safe code.
    btw AddRef, Release, Attach and Detach are just the names of functions used with COM objects and CComPtr, which I use on a daily basis.

    So, what owns the instance of that class in the smart pointer? The best answer I can give is that its ownership is shared amoung all things that are responsible for decrementing the refcount when they're done with it, and have not yet done so.
    Ownership = Responsibility to delete.

    Well the thing you've been asking all along (thread title) is simply not possible in any way. The only way to ban using the things you mention with your smart pointer class, is to ban your smart pointer class and start using ones already written by the experts instead!
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  3. #48
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Quote Originally Posted by Elysia View Post
    I'm talking about creating new instances on the heap, not on the stack. Making the constructor private will make it impossible to create the object at all.
    Not quite...
    A static member function of that class can be used as a factory method for creating and returning instances of the class. Or of course, adding another class as a friend will work.
    It's not what you want to do though I imagine.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  4. #49
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by iMalc View Post
    Unfortunately there are a number of things that simply become necessary when dealing with anything that is shared amoung threads. For example, altering the refcount of the smart pointer can no longer be done with a simple refCount++. You have to use InterlockedIncrement, a CriticalSection, or something like that. You also start needing to use the 'volatile' keyword.
    For refcounting you perhaps already need to use the 'mutable' keyword if you write const-correct code.
    Yes indeed to thread safety. I've guarded all public functions with a critical section if thread class safety is enabled.

    Quote Originally Posted by iMalc View Post
    Yes if you've made AddRef and Release (or whatever you called them) private, which is fine, then you would quite possibly need some other way of dealing with passing to a thread.
    There is no AddRef for a obvious reason and you can Delete (say you don't need the memory any longer) which decrements the ref count, which is public. Only the copy constructor increments the ref count. That is, when a new copy of the variable is created.
    Of course, you can always assign another pointer (wrapper by the class of course) to another, in which case it will copy the reference counter over, as well (plus increasing by one).

    Quote Originally Posted by iMalc View Post
    You'll find that most smart pointer classes allow some way of transferring ownerhip through calls to or from functions that can't take a smart pointer, such as by having a Attach and Detach functions. External functions such as CreateThread that do not let you pass the type of object you want by value are precisely when you need such things. Using Attach and Detach instead of direct calls to the objects AddRef and Release methods tends to mean cleaner and exception-safe code.
    btw AddRef, Release, Attach and Detach are just the names of functions used with COM objects and CComPtr, which I use on a daily basis.
    This actually gave me an idea. Perhaps it's possible to create a static map within the memory manager (so it's shared between all copies of the class) to keep track of the address of the reference counter. That way, it can lookup a pointer and thus find the reference counter and increase it.
    Wow, that will even make it possible to assign a raw pointer directly.

    Quote Originally Posted by iMalc View Post
    So, what owns the instance of that class in the smart pointer? The best answer I can give is that its ownership is shared amoung all things that are responsible for decrementing the refcount when they're done with it, and have not yet done so.
    Ownership = Responsibility to delete.
    Indeed. Only all functions can only tell the class they no longer want the memory, so no function really has the responsibility to delete the memory. As long as the reference counter is correct, you can call Delete() anywhere you want, whenever you want, and it will be safe to do so.

    Well the thing you've been asking all along (thread title) is simply not possible in any way. The only way to ban using the things you mention with your smart pointer class, is to ban your smart pointer class and start using ones already written by the experts instead!
    Banning references is not possible, you say, but banning pointers is possible.

    Quote Originally Posted by iMalc View Post
    Not quite...
    A static member function of that class can be used as a factory method for creating and returning instances of the class. Or of course, adding another class as a friend will work.
    It's not what you want to do though I imagine.
    I'll think about that... That would make it impossible to create an object that isn't exposed through methods.

  5. #50
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by Elysia View Post
    I suppose I can do like I've done with CMemoryManagerNew and CMemoryManagerDefault. Create a new class, inherit from CMemoryManager, but override the protected operators with public ones.
    Only to be used with things such as STL.
    Actually with the STL it's easier. Just make an friend allocator for your object.

    But by disallowing pointers you are more or less saying that you don't want the object to be contained in containers like dynamic arrays.

    Speaking of arrays, stack arrays are a simple way to create a pointer to your object.
    Edit:that's stack arrays


    And for the record, I agree that there isn't much use for all this. You are correct in pointing out that a problem arises if an object pointed to by a smart pointer can be freed outside the function. However there is an equivalent problem that concerns the smart pointer itself: The smart pointer or reference can itself be freed outside the function. Thus there exists a responsibility of a function passing an argument by pointer or reference to ensure that the object remain valid. If such a responsibility already exists, it is natural to extend it to apply the value pointed to by a smart pointer passed by reference.

    In other words, passing smart pointers by pointer or reference is no more dangerous than passing anything else by reference, so there is no point in putting special protection on smart pointers alone.
    Last edited by King Mir; 10-28-2007 at 01:51 AM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  6. #51
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I'm tired of discussing the pitfalls and bad ins or outs of this matter.
    If there is any way someone can think of, then I'm interested. I want to try and see what happens.

  7. #52
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by Elysia View Post
    I'm tired of discussing the pitfalls and bad ins or outs of this matter.
    If there is any way someone can think of, then I'm interested. I want to try and see what happens.
    It doesn't look like there is any way to block references the way you can with pointers.
    The only way I could imagine doing it would be by overloading the object reference operator, but as you'll see from this code (if you compile & run it), it doesn't work:
    Code:
    #include <iostream>
    
    using namespace std;
    
    class NoRefs
    {
    public:
    	NoRefs() { cout << "NoRefs constructor called." << endl; }
    	~NoRefs() { cout << "NoRefs destructor called." << endl; }
    	void Print( const char* str ) const { cout << str << endl; }
    
    private:
    	operator NoRefs&();
    	operator const NoRefs&() const;
    };
    
    
    void CRefFunc( const NoRefs& ref )
    {
    	ref.Print( "In CRefFunc()" );
    }
    
    void RefFunc( NoRefs& ref )
    {
    	ref.Print( "In RefFunc()" );
    }
    
    void ValFunc( NoRefs val )
    {
    	val.Print( "In ValFunc()" );
    }
    
    int main()
    {
    	NoRefs obj;
    
    	ValFunc( obj );
    	RefFunc( obj );
    	CRefFunc( obj );
    
    	return 0;
    }

  8. #53
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Yeah, I believe I've already tried that.

  9. #54
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    You can't block creating pointers either. You can just make it more difficult. But even if you hide operator &, I can use the addressof macro (in Boost) to obtain a valid pointer, and then safely cast it back to your type:
    Code:
    weird_type wt;
    weird_type *pwt = static_cast<weird_type *>(addressof(wt));
    By the way, on the first page you wondered why making operator delete private prevents you from using new. The reason is the way construction works. Suppose placement new is not overloaded, but new and delete are:
    Code:
    // This:
    foo *p = new foo;
    // is translated by the compiler to something semantically equivalent to this:
    void *p_mem = foo::operator new(sizeof(foo));
    try {
      p = new (p_mem) foo;
    } catch( ... ) {
      foo::operator delete(p_mem, sizeof(foo));
    }
    In other words, the compiler allocates memory, then calls the constructor, passing the newly allocated memory as the this pointer. If the constructor throws, the compiler has to free the memory, because the programmer never gets to see the address of the block and thus can't handle the deallocation.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  10. #55
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by CornedBee View Post
    You can't block creating pointers either. You can just make it more difficult. But even if you hide operator &, I can use the addressof macro (in Boost) to obtain a valid pointer, and then safely cast it back to your type:
    Code:
    weird_type wt;
    weird_type *pwt = static_cast<weird_type *>(addressof(wt));
    Well, at least I can make it hard for people without using asm or workarounds.

  11. #56
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Elysia View Post
    Well, at least I can make it hard for people without using asm or workarounds.
    Now, there's a difference between preventing the user from hurting himself accidentally, and deliberately getting in the way. Trying to absolutely forbid the taking of a pointer is ultimately impossible and is basically a pointless arms-race with the user programmer. Put some orange traffic cones around the hole in the road -- no need to guard the area with a robot army.

  12. #57
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Yes, it is. I might as well try it for myself, and if anyone else might want it, I could just block the most important things that can make things go wrong and put warnings lights saying the behaviour is not intended or might produce undefined behaviour.

  13. #58
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Still, in the end, your example use case comes down to trying to use a thread-affine object from multiple threads.

    Forbidding references and pointers is not the way to make thread-affine objects safe. What it achieves is preventing the (useful) use of references and pointers. (One example: if your smart pointer is thread-safe, then copying has the thread-safe reference counting overhead. If you want to avoid that, you could pass the smart pointer by reference within any single thread, which is perfectly safe and faster than copying it.)

    To enforce thread affinity, you need to check thread affinity. Basically, in the debug version of your program, the smart pointer should grab the executing thread's ID on construction and assert() on every dereference that the executing thread's ID is the same as the one taken on construction. That is (assuming N2320 C++ threading):
    Code:
    #if defined(NDEBUG)
    #define TEST_AFFINITY()
    #define INIT_TID()
    #else
    #define TEST_AFFINITY() assert(m_tid == std::this_thread::get_id() && "Thread affinity violation.")
    #define INIT_TID() m_tid(std::this_thread::get_id()),
    #endif
    
    template <typename T>
    class ta_ptr // thread-affine
    {
    #if !defined(NDEBUG)
        std::thread::id m_tid;
    #endif
        T *m_ptr;
        std::atomic<int> *m_pcount;
    
    public:
        ta_ptr() : INIT_TID(), m_ptr(0), m_pcount(0) {}
        ta_ptr(T *ptr) : INIT_TID(), m_ptr(ptr), m_pcount(new std::atomic<int>(0))
        {} // Note: not exception-safe.
        ta_ptr(const ta_ptr &o) : INIT_TID(), m_ptr(o.m_ptr), m_pcount(o.m_pcount) { ++*m_pcount; }
    
        ta_ptr &operator =(const ta_ptr &o) {
            TEST_AFFINITY(); release(); m_ptr = o.m_ptr; m_pcount = o.m_pcount; ++*m_pcount;
        }
    
        T & operator *() const {
            TEST_AFFINITY(); assert(m_ptr); return *m_ptr;
        }
    };
    
    #undef TEST_AFFINITY
    #undef INIT_TID
    You get the idea. Now, foo2 ought to assert with a thread affinity violation instead of an access violation. (Of course, the fact that you reference a local object in another thread means that the object itself has become invalid, so foo2 might still produce an access violation, but you might be lucky.)
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  14. #59
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by CornedBee View Post
    Still, in the end, your example use case comes down to trying to use a thread-affine object from multiple threads.
    Unless there are more examples.

    Quote Originally Posted by CornedBee View Post
    Forbidding references and pointers is not the way to make thread-affine objects safe.
    Already been mentioned several times.

    Quote Originally Posted by CornedBee View Post
    What it achieves is preventing the (useful) use of references and pointers. (One example: if your smart pointer is thread-safe, then copying has the thread-safe reference counting overhead. If you want to avoid that, you could pass the smart pointer by reference within any single thread, which is perfectly safe and faster than copying it.)
    You have a point, but I designed the class to be thread safe or not. User choice. If no thread security is done, it's faster and not needed on multiple threads.
    You might have a point in passing by reference if it's thread safe and multiple threads, of course, but I don't like the idea that another function will be using the memory without increasing the ref counter.

    Quote Originally Posted by CornedBee View Post
    To enforce thread affinity, you need to check thread affinity. Basically, in the debug version of your program, the smart pointer should grab the executing thread's ID on construction and assert() on every dereference that the executing thread's ID is the same as the one taken on construction. That is (assuming N2320 C++ threading):
    That's another excellent idea! I will add checks if the class is put to not be thread safe is the creating thread is actually trying to access the object.

    UPDATE:
    I actually thought of something else that might put references at risk.
    One is calling Delete() from a function:
    Code:
    void foo(pp<int>& pTest)
    {
    	// ...
    	pTest.Delete(); // Oops, decrements the reference count and destroys the memory.
    	// ...
    }
    
    int main()
    {
    	ppnew<int> pTest;
    	foo(pTest);
    	int n = *pTest; // Oops, access violation, pTest == NULL
    	// ...
    }
    Another reason why passing by reference might not always be such a good idea. I know this is stupid and you should probably (or maybe) assign cost, but anyway, here goes:

    Code:
    void foo(pp<int>& pTest)
    {
    	pTest = newpp(new int, false); // newpp(T* p, bool bArray); // NOT INTENTIONAL! Supposed to be used locally, but isn't due to being passed by reference.
    	*pTest = 1;
    }
    
    int main()
    {
    	ppnew<int> pTest(0);
    	int n = *pTest; // n == 0
    	foo(pTest);
    	int n = *pTest; // n == 1
    }

  15. #60
    The larch
    Join Date
    May 2006
    Posts
    3,573
    I fail to see how either of these demonstrates a problem with references. 1) Functions shouldn't destroy resources randomly. 2) The second example is a case where you'd want to modify pTest in foo.

    It's really up to the programmer of foo to decide whether they want to receive the parameter by value, by reference, const reference or pointer (if null pointers are OK). You shouldn't disable useful features of the language because you expect the user programmer to be an idiot who can't tell the difference between a copy and a reference.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Pointers v References
    By m37h0d in forum C++ Programming
    Replies: 28
    Last Post: 06-30-2008, 01:29 PM
  2. Replies: 12
    Last Post: 12-31-2007, 06:59 AM
  3. Pointers, Classes, and Errors o my!
    By Scottc1988 in forum C++ Programming
    Replies: 12
    Last Post: 03-13-2003, 10:14 PM
  4. Pointers to derived classes.
    By sean in forum C++ Programming
    Replies: 3
    Last Post: 11-13-2001, 08:19 PM
  5. Pointers to inherited classes
    By sean in forum C++ Programming
    Replies: 1
    Last Post: 11-03-2001, 03:04 PM