How to deal with pointers without smart pointers?
I'm reading C++ Primer Plus by Stephen Frata. I've read up to chapter 6, which means I learned about pointers but not about objects and classes (Although I know about OOP).
I came from a ActionScript (Flash) and Java background, so I never dealt with pointers before, but I understand them. I have a bunch of questions about them though.
As I understood, you need to pair new and delete, i.e. the object/function that creates a pointer is responsible for freeing it. But imagine a simple factory function, like this :
SomeObject * createSomeObject(){
return new SomeObject;
}
That looks pretty problematic. Who is responsible for freeing this pointer now?
What if I create a class that gives public access to a pointer that it created. Following the new/delete rule, this class should be responsible for freeing the pointer in its destructor. But since the pointer might be used by another class, d开发者_如何转开发estroying the first class would breaks the second...
Those two interrogations are similar. What can I do to manage a pointer that is known to other entities than the one who created it?
Note : I'm aware that smart pointer could solve this problem, but I'm wondering how people do without them.
"Who is responsible for deleting it?" is a very good question. Generally with a function like that, you would simply document that the returned pointer must be deleted. It is up to the user of the factory to determine which class or function is responsible. However, this is a bit vague, and is indeed a problem.
In modern C++ style, this is exactly why smart pointers are used. Consider:
std::unique_ptr<SomeObject> createSomeObject() {
return new SomeObject;
}
In this case, the pointer is owned by the returned unique_ptr
. Wherever you move it, the stored pointer is deleted in its destructor (when it scopes out or when the object containing it destructs). This makes obvious which part of the code is responsible for destroying it and the delete happens automatically (so you can't forget to delete it or make some "destroy" call), so is considered the solution to the above problem.
Memory-management issues, and "ownership" and lifetime of objects, heavily influence design in C++. Generally, smart-pointers and similar techniques are preferred.
However, if you don't want to use smart pointers, etc., then you just have to be very rigorous. Generally, memory-management of a particular object should occur across one interface. So any function that creates a heap-based object (such as your createSomeObject()
) should have a matching function that deletes the object (e.g. deleteSomeObject(SomeObject *)
). Of course, there are always exceptions to this sort of guideline.
This, and good documentation, minimises the chances that someone will screw up and cause a memory leak.
There are different approaches to this:
One is "don't allocated on the heap:
SomeObject createSomeObject(){
return SomeObject();
}
If you do this instead, no one has to free the object, and you have no pointers to worry about. A potential downside is that SomeObject
must be copyable, but often this is a good solution, and it should generally be your default. Don't use new
/delete
in user code, hide them inside constructor/destructor calls. (For example, maybe SomeObject
allocates some data on the heap internally, and frees it when the object itself is destroyed).
The second approach is related, but uses a smart pointer:
std::shared_ptr<SomeObject> createSomeObject(){
return std::make_shared(new SomeObject());
}
this is similar in that you're not returning a pointer, you're returning an object which is responsible for deleting anything that needs to be deleted. The smart pointer has taken ownership of your SomeObject
instance, and will delete it when appropriate.
Depending on circumstances, std::auto_ptr
or std::unique_ptr
might be preferable.
In both cases, you're relying on RAII, a very powerful idiom that every C++ programmer should know. Resources should always be wrapped in local (not heap-allocated) objects, which are copied and moved around as appropriate, and are responsible for cleaning up their internal resource(s).
That looks pretty problematic. Who is responsible for freeing this pointer now?
I suggest pair-functions.
Xyz* CreateXyz();
void DestroyXyz(Xyz *xyz);
Abc* NewAbc();
void DeleteAbc(Abc *abc);
Or you simply can transfer the responsibilty of deleting Xyz/Abc
to the clients, i.e ones who call the function must also do delete on the returned object after using it.
Whatever you choose, make it clear in your documentation how the created object should be destroyed.
I would prefer pair-functions, especially if there is lot of things to consider before deleting!
EDIT: I suggest this pair-functions approach, when you build a DLL or some dynamic library. Actually this ensures that the object is destroyed from the same memory pool from which it was created!
There's no one, who is responsible for creating/destroying.
You define the logics.
If you did allocated the pointer to 512 bytes to store 128-char long string, successfully changed the string and saved it to file, you can destroy the pointer any time after that.
It is not always true in C++ that "the object/function that creates a pointer is responsible for freeing it," as you say. In class factory, usually code that uses the object deletes it if you can ensure that there are no additional users of the pointer. Otherwise, a smart pointer or some reference counting sceme is required.
Usually a factory function is not responsible for deleting the objects it created. If you impose the guideline of new/delete all in one class it would be virtually impossible to return pointers at all - especially in multi-threaded environments.
In most cases the documentation/reference explicitly specifies who is responsible for deleting an object if it not obvious. Possible solutions without handling this manually would go into the field of garbage collection I think (reference counters being a popular approach).
There's not any simple answer to that which would work in every situation. There are always exceptions to rules. You'll just have to consider different architectures to your software and decide which one seems like the easiest to understand to the person who reads the code.
It is basically impossible to write exception safe code in C++ if there are naked new & deletes.
So, you have basically two choices:
- smart pointers to wrap the allocated resources.
- avoid the use of naked new & delete by designing your classes around a reference counting wrapper class idiom. Actually this is really just another name for smart pointers, done the hard way.
The basic idea is, you create in your framework a set of classes that are cheap to copy, that wrap up another class that holds a reference count and a resource that would be expensive to copy.
By providing assignment and copy operators on the cheap to copy class, you get a data structure that can be passed around with the same semantics as a raw type, like an int.
It is little more than a re-invention of smart pointers, but I like it as the syntax is less cluttered than using non-typedef'ed smart pointers in code, and it is somewhat satisfying to know enough about c++ constructors and operator overloading to write the wrappers.
Jalf has already mentioned the important bit, i.e. that you don’t necessarily allocate on the heap.
Something else plays into this, and the book probably won’t tell you this:
C++ isn’t particularly good at OOP. Despite being originally designed as C with classes (which at least implies its OOP goals), OOP is actually very cumbersome in C++, and not the focus in most modern C++ idioms.
If you’re tied to a OPP framework you essentially have two choices:
- Smart pointers, or
- A garbage collector (I mean this in the broadest sense – a custom (e.g. pool) allocator that magically manages its resources can also be counted here).
Usually, you try to do without explicit pointers and freestore management in C++. That is, you allocate your business objects on the stack and encapsulate all dynamic memory management into them via RAII.
Of course, this makes subtype polymorphism much harder to achieve. But to mitigate this, C++ offers a very powerful templating mechanism that lets you write extremely abstract, high-level code (arguably more so than OOP). This is sometimes called algorithms oriented, to offset it from object orientation. The C++ <algorithms>
header and the standard library containers are a good example of this.
精彩评论