Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

With the advent of smart pointers, is it a sign of poor design if I see objects are deleted? I'm seeing some software components in our product that people are still doing this. This practice strikes me as un-idiomatic, but I need to be sure this is the industry consensus. I'm not starting a crusade but it'd be nice to be prepared theory wise.

Edit: legit uses of delete, Klaim mentioned the object pool use case. I agree.

Bad examples of using delete, I am seeing many new's in constructor or start() and corresponding delete's in the destructor or stop(), why not use scoped_ptr? It makes the code cleaner.

share|improve this question
    
delete is an operation that doesn't exist in C (neither in Java or C#)- How could it be unidiomatic in C#? Maybe it could be considered deprecated, though. – user281377 Mar 13 '11 at 17:01
4  
@ammoQ: delete is necessary, because smart pointers and containers have to be implemented somewhere. I think the question should be: Should they be used in application code, or only in low-level libraries? – nikie Mar 13 '11 at 19:33
3  
I feel this question belongs on SO, even though it's a tad subjective. – Johannes Rudolph Mar 13 '11 at 20:57
    
I am not sure this question deserves its close votes - RAII and smart pointers are very clearly defined and have agreed-upon semantics that provide a properly-scoped, objective answer to this question. – Snowman Jan 5 at 19:12
up vote 9 down vote accepted

Short answer : that depends, and using smart pointers systematically is just wrong. Think first. I'm using smart pointers for a lot of things but it's not right for everything, ie. no silver bullet. You'll have to understand your specific implementation to understand if it's wrong or not. I'm giving some examples in the ...


Long answer :

What makes a software poor, regarding object lifetime, is only the lack of clear and precise control.

C++ letting you define the lifetime of objects mean that the programmers have to setup ways to manage those lifetimes, how different they could be and how easy it is to change it.

I know a lot of cases where smart pointers are just the wrong answer (or overkill), starting with objects in pools. If objects are managed inside a "master" object that will do the new and delete called in an isolated way, then that's fine. Don't forget that smart_pointers, like any other techniques, only hide deletes in a manageable way. To achieve this, they just make clear when the delete will be called and make it a rule.

So, the idea here is that as far as the delete call is put in one place, easy to find, easy to understand, etc. and that it's obvious that people who wrote the code did want the rules of deleting the object to be uniform (no delete hidden in a "special case" code), then it's not poor software design.

Smart pointers are meant to be the "easy answer" to a range of cases where you can't be sure where the delete call should be done. So you have to define how to delete it and define a rule that trigger this delete. Shared pointers delete once there is no reference to the object. Scoped pointers delete once out of scope. etc. It's easy to use and solve a lot of cases.

But as every tool, it's not silver bullet. As said previously, you can't provide smart pointers for objects allocated in pools. In video games, you often "know" precisely how much objects of each types are allowed at the same time, and the frequency of creation/destruction of those objects. So why do new and delete in this case? You just need to new all the objects in raw memory, use them and delete everything at the end, or just dump the raw memory.

In fact, almost all choices in those cases are driven by hardware or safety or other constraints.

There are no hard and fast rules, just good solutions to specific problems. Especially in C++ as you're the one in charge, not a VM.

If you feel a code smell about your specific case, it might be because the delete calls are done in special or specific cases, not in a generic way. That is poor design. Another thing that should smell is if new and delete are done while there is no good reason to use heap memory instead of stack one. The obvious case is if an object is created and destroyed in the same function. The only case where new/delete is valid then, is if the object requires more memory than the stack allows (and that does happen!).

So, just try to understand exactly why those deletes happen where they do, and if there's no good reason for them being there, you should refactor (if possible).

share|improve this answer
    
Okay I buy the object pool case. – grokus Mar 13 '11 at 20:45
1  
good point about pools, it's why I prefer to refer to smart managers rather than smart pointers, the latter being too limited. A number of pools though will give you a handle rather than a pointer, which is especially handy for debug modes (as the handle can provide a number of checks, like making sure you're not accessing a deleted object), or if you want reference counting on your object (which is akin to smart pointers). – Matthieu M. Mar 14 '11 at 7:14
    
Well, that smart manager is just a collection of smart pointers, or at least a convincing emulation. – Deduplicator Jan 3 at 18:28
1  
What kind of pool are we talking about here, and why wouldn't I want the pool to deal out smart pointers (handles) that automatically return the object to the pool once I'm done? – Sebastian Redl Jan 3 at 19:52
    
Object pool case is irrelevant, as nothing prevents use of smart pointers inside it, while providing simple references as interface. No object pool should encourage manual deletion. – Basilevs Jan 4 at 6:42

I think there are valid reasons for using delete manually, especially if you have to interface libraries that don't use smart pointers, or a set of libraries that use different smart pointer types. But this should be the exception, not the rule. If you can replace the manual delete with a smart pointer, you should.

share|improve this answer

Not every piece of software needs to use smart pointers. Depending on the smart pointer implementation and on the application at hand, they can actually cause more trouble than they are worth.

In some situations you'll find that a given object owns another object completely for it's entire lifetime. At which point, introducing a smart pointer to delete something that can easily be deleted in the owner's destructor may not be all that useful.

Older apps that have been 'brought forward' into the C++ era often don't have smart pointers either. These things started out without objects, and were hammered on until they had objects in them. These apps often have some seriously idiosyncratic behavior and design, and smart pointers may simply not be a part of it.

Containers of pointers may also not be done with smart pointers because (again, depending on the available smart pointer implementation), they may do odd things to the access patterns of the contained objects.

The biggest reason a given application doesn't have smart pointers is simply that the original author wasn't familiar with them, and/or didn't have a smart pointer implementation that they liked. It's not any indication of the quality of the surrounding code.

share|improve this answer
    
(1) For the one object owns another object case, a scoped_ptr works pretty well. (2) I don't see any advantage of using raw pointers over shared_ptr in a container. – grokus Mar 13 '11 at 21:00
    
(2) Some smart pointers make indirections look odd in containers. I haven't used shared_ptr, that one probably doesn't, so it may well be a good fit. Also, if one's first experience with 'smart' pointers was with auto_ptr, one gets into the habit of not trying anything too fancy. – Michael Kohne Mar 14 '11 at 1:42
    
(1) I just tend to think that in these simple cases where ownership is well established, adding yet another object isn't worth the trouble. But that's a personal preference. – Michael Kohne Mar 14 '11 at 1:42

RAII and Exceptions

With the advent of smart pointers, is it a sign of poor design if I see objects are deleted?

It depends on the context. Let's have a look at an example like this:

Foo* foo = new Foo;
Bar* bar = new Bar;
func(foo, bar);
delete foo;
delete bar;

* Note: not utilizing the stack in this example is silly. Let's imagine in the real world that Foo and Bar are pimpls, or are ultimately abstract types to be stored/owned in a polymorphic data structure, are created through a factory, are destroyed only when the user requests to unload the resource, etc.

Is there anything wrong with this code? Some people might think, "no". Except there's all kinds of problems with this code, and not relating at all to practices or style -- I mean this code is buggy. The problem is this:

Foo* foo = new Foo; // this can throw
Bar* bar = new Bar; // this can throw
func(foo, bar);     // this might throw
delete foo;
delete bar;

It's the interaction of manual release of resources with exception-handling that makes destructors that automate this often an absolute necessity (see RAII). To make this above code no longer buggy for exceptional paths without reaching for destructors to automate it, we might need to do something like this:

Foo* foo = new Foo;     // this can throw
try
{
    Bar* bar = new Bar; // this can throw
    try
    {
        func(foo, bar); // this might throw
    }
    catch (...)
    {
        delete bar;
        throw;
    }
    delete bar;
}
catch (...)
{
    delete foo;
    throw;
}
delete foo;

... phew! That should, provided I didn't make any mistakes (which is still very possible), fix the bugs. Another way is like this:

Foo* foo = new Foo;     // this can throw
Bar* bar = 0;
try
{
    bar = new Bar;      // this can throw
    func(foo, bar);     // this might throw
}
catch (...)
{
    delete foo;
    delete bar;
    throw;
}
delete foo;
delete bar;

... which might be a little bit easier, but still pretty complicated (and imagine how easy it is to mess this all up if we came back and made changes to it later, or if one of our teammates did).

Now let's consider how we can fix the bugs using smart pointers:

unique_ptr<Foo> foo(new Foo);
unique_ptr<Bar> bar(new Bar);
func(foo.get(), bar.get());

Tada! I don't know about you, but that seems a whole lot easier to read, write, manage. Through destructors which automatically release resources, we end up making our lives so, so, so much easier.

Exception-Safety

Some people might think the above example is kind of moot, since it's fixing bugs in exceptional paths of execution. For toy programs at least, worrying about correct behavior in the face of an exception might be overkill.

Yet for production code, I work in an area which is not mission-critical that much, but people still tie their professional livelihood (not lives) to the software. It pays, in these cases, to give some attention to exception-safety, to allow the software to gracefully recover in the face of exception (and also write tests that simulate exception paths by throwing while checking against leak detectors and such). Destructors which automatically release resources and/or roll back side effects are a lifesaver here when interacting with exceptions.

Smart Pointers

That said, do we have to use smart pointers everywhere, per se? Here I'll offer a biased answer that might be unusual as one working on very low-level, performance-critical areas.

There's no cost to something like std::unique_ptr provided we don't need a custom deleter. But often in these areas I work the most in, we use custom allocators, requesting chunks from a memory pool and returning them when we're done. Often my code doesn't look like this:

Foo* foo = new Foo;
...
delete foo;

Instead it looks like this using placement new:

Foo* foo = new(allocator.allocate()) Foo;
...
foo->~Foo();
allocator.deallocate(foo);

With these kinds of cases, it can get a bit awkward to use smart pointers. The standard ones tend to have an overhead when custom deleters are involved which defeats some of the purpose of using fixed allocators and such to improve locality of reference (fewer smart pointers fitting into a cache line). Plus the code just gets a bit more awkward.

It's impossible in these cases to even roll my own smart pointers without paying a runtime cost for it, since they'd need to be injected with the custom allocator I'm using across individual objects, and that would at least equate to doubling the size of the smart pointer (I work in areas like graph data structures where shaving 8 bytes per instance can make the difference between 1.2 gigabytes of memory and a gigabyte, e.g.).

That said, this is no excuse to avoid RAII. So what I do is very carefully still free memory in my destructors (for whole data structures, not for each element inside), to wrap this kind of code into objects that manage memory through their constructor and destructor, and still benefit from RAII that way. The resource management is still automated to those using my classes, I just have to be careful and implement my destructors with manual memory management involved and write tests to make sure I don't forget to free memory in a destructor.

This is an exceptional case: for most people, I'd say it's pretty wise to just use smart pointers as much as possible for pointers owning memory if the alternative strategy is using default, throwing operator new and operator delete. It's only when custom allocators are involved that things get a bit murky.

share|improve this answer
    
{ Foo foo; Bar bar; func(foo, bar); } would have been better.. – Lightness Races in Orbit Jan 3 at 23:20
    
@LightnessRacesinOrbit Argh, I had a hard time balancing the simplicity of the example versus trying to come up with a genuine reason to use the heap without going into polymorphic containers, pimpls, factories, more complex and asymmetrical allocation/deallocation patterns, things like that -- so I just ended up adding a caveat that we should normally use the stack in these simplistic examples, but to sort of imagine a scenario where we need the heap. – Ike Jan 4 at 2:19

Yes. If it's designed now.

If it's been designed before their advent, nothing should prevent you from replacing the manual handling by smart managers.

The question is not:

Should I be using smart managers here ?

But:

Which smart manager fits best ?

If a modern piece of code is written without smart managers, it's an indication that the author is not up-to-date on C++ practices, and I would be very worried about the exception safety aspect....

share|improve this answer
1  
Smart points do not guarantee exception safety either. – quant_dev Mar 13 '11 at 20:51
1  
@quant_dev: and I did not say so. However they are a tremendous step toward guaranteeing Basic Exception Safety, which is about not leaking resources even in the face of exception. Without them, even if the code is littered with try/catch, you'd better worry about its behavior in this regard. – Matthieu M. Mar 14 '11 at 7:07

At the moment, there is a lack of smart pointers in the C++ standard library, and Boost can't always be used, so until C++0x delete will be necessary.

Revisiting old code that works is pointless, irrespective of the evolution of idioms.

Even with a good set of smart pointers, you probably won't want to use them if the whole point of your code is to implement a data structure that isn't provided by the C++ standard library, among a few other special cases, so delete will always be needed in some library code.

A point worth considering - smart pointers can have issues with reference cycles. If your objects may have reference cycles, using smart pointers is likely to cause a memory (and potentially resource) leak. Using a smart pointer can be wrong, or at least cause complication for no practical benefit, in some rare special cases.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.