12

I'm a bit new to C++ and been doing programming in Obj-C and Java up until now.

Say, I have a class:

class Person {

private:
   Wife *current_wife;
   //.....
};

So obv I need to implement a setter method to change the Wife instance variable.

Like this:

Person::SetCurrentWife (Wife *new_wife) {

    current_wife = new_wife;
}

That would be a shalllow copy.

So somewhere from the main loop or something I call:

Person *some_person = new Person();
...
Wife *wife = new Wife ();
some_person->SetCurrentWife(wife);

So I'm confused: will there be a memory leak here? Should I delete the wife object here or in Person's destructor? In Obj-C I would release the current wife and then send a retain message to wife object above, but what is the proper way of doing setter methods in C++?

Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
Random
  • 203
  • 1
  • 6
  • 10
    Since you're new to C++, heed this advice: never use `new`, `delete`, or pointers. As an exception, you *may* use `new` inside the constructor of a smart pointer, but only after you have determined that you really do require dynamic storage. – Kerrek SB Dec 20 '11 at 13:29
  • 4
    Kerrek forgot to recommend [a good introductory C++ book](http://stackoverflow.com/q/388242/46642) :) – R. Martinho Fernandes Dec 20 '11 at 13:31
  • 4
    In OO you don't obv need a setter, you need functions to let the object do something (divorce, remarry). Setters may be convenient, they're not obv. – stefaanv Dec 20 '11 at 13:34
  • 15
    The questions you have to ask yourself are: "Does a `Person` own his `Wife`?" and "When a `Person`'s lifetime ends, should his `Wife` be automatically destroyed?". – CB Bailey Dec 20 '11 at 13:35
  • @Kerrek SB: Good advice! Here are a reference to why if anyone would be interested in why: http://stackoverflow.com/questions/6500313/in-c-why-should-new-be-used-as-little-as-possible – rzetterberg Dec 20 '11 at 13:36
  • @Charles: obviously software objects shouldn't be confused with real-life objects ;) – stefaanv Dec 20 '11 at 13:38
  • 2
    @stefaanv Software objects (in the OO sense, not in the C++ standard sense) often model real-life objects, or some aspects of real-life objects. I can't imagine a program where an object representing my `Wife` would be destroyed because I die. – James Kanze Dec 20 '11 at 14:17
  • 3
    The problem with any answer that kneejerk-cries for "smart pointers" is that they're missing the point - the *first* question in the design process shouldn't be how to implement dynamic storage, but *whether you need dynamic storage at all*. This is one of the biggest crimes I hold Java and any of this "modern OO" responsible for: It has completely eroded people's ability to think about the underlying *logical structure* of their data. *First* you think about lifetime, and only then do you proceed to implement the required semantics. – Kerrek SB Dec 20 '11 at 14:21
  • @JamesKanze: That is 1 of the issues with OO: people try to model real-life too much, especially when teaching OO. You need to model based on your problem domain to work towards a solution. The joke of course being "When a Person's lifetime ends, should his Wife be automatically destroyed?" – stefaanv Dec 20 '11 at 15:38
  • @stefaanv You don't want to model real-life too exactly, but a number of classes in a typical application will model specific aspects of real-life. At least if the application is to have any applicability in real-life. (But this is all beside the point. The idea is just to use it as a lever for further discussion of possible solutions. There is no one absolute solution for all cases; the OP has to consider the larger design. And IMHO, the issue of lifetime has to be considered. Regardless of the language, smart pointers, garbage collection or whatever.) – James Kanze Dec 20 '11 at 16:08
  • That was merely an example . And yes, I do need to use dynamic memory. Thanks for the helpful answers (both these and below), folks. – Random Dec 20 '11 at 17:26

3 Answers3

7

It depends on what you're trying to do. First, as Kerrek SB has commented, you don't want to use pointers if value semantics can be applied: if Wife is copyable and assignable, there's almost no reason to allocate it dynamically. In this case, however, I'm guessing that Wife derives from Person (although perhaps a decorator for Person would be more appropriate, since the fact that a given Person isA Wife can change over time), that there might even be types derived from Wife (and that Person::current_wife might want to hold one of these), and that in fact, Wife has identity; you don't want copies of the same wife all over the place.

If this is the case, then you really have to define a protocol for the interactions of other classes with Wife. Typically, the lifetime of Wife will not depend on the Person who holds it (although if it is a decorator, it might), so Person should just hold a pointer to it, as you've done. Most likely, the Wife object will have various functions which—implicitly or explicitly—control its lifetime: you might have something like:

void Wife::die()
{
    //  ...
    delete this;
}

for example. In that case, however, whoever is married to Wife will have to be informed, so that current_wife doesn't point to a dead spouse. Typically some variant of the observer pattern can be used for this. (Note that you have exactly the same issue in Java; you don't want Person::current_wife to point to a dead Wife. So you'd still need a Wife::die() function, and the observer pattern to notify the spouse.)

In cases like this (which in my experience represent the vast majority of dynamically allocated objects in C++), about the only difference between C++ and Java is that C++ has a special syntax for calling the “destructor”; in Java, you use the usual function call syntax, and you can give the function any name you wish (although dispose seems widely used). The special syntax allows the compiler to generate additional code to release the memory, but all of the other activities associated with the end of the object lifetime still have to be programmed (in the destructor, in C++—although in this case, it might make sense to put some of them directly in the Wife::die function).

James Kanze
  • 142,482
  • 15
  • 169
  • 310
4

You should use a smart pointer.

If you use regular pointer, practice caution!

You should delete the old current_wife member both on the destructor and on the set method. Setting a new wife will lead to the memory leak of the old one, as the pointer to that allocated memory is lost (unless you manage the memory outside the class - see below).

But even doing so, you need to make sure that no-one outside the class can delete the member. You have to decide whether memory management is left to the class or dispached to the outside of the class, and stick to it.

Luchian Grigore
  • 236,802
  • 53
  • 428
  • 594
  • 3
    -1 Because it's simply bad advice. I'd be very surprised if smart pointers implement the desired lifetime semantics---you really don't believe that it is impossible for my wife to die before I do (or that my death should trigger hers), do you? – James Kanze Dec 20 '11 at 14:15
  • @JamesKanze so why would that be a problem? Would using smart pointers restrict you from deleting the wife object beforehand, or deleting it after person has died? – Luchian Grigore Dec 20 '11 at 14:29
  • it depends on the smart pointer, but if you delete something managed by any of the standard smart pointers, you're in undefined behavior land. If you use `shared_ptr`, especially, the lifetime of the object is out of your hands. – James Kanze Dec 20 '11 at 14:31
  • @James: This is probably culture-specific. It is in general bizarre to have a Person class and then a Wife class. In some cultures Persons marry other Persons. – UncleBens Dec 20 '11 at 15:48
  • @UncleBens The whole structure is a bit dubious: if `Wife` derives from `Person`, then some people are wives from the day they're born to the day they die. And if `Wife` doesn't derive from `Person`, one wonders about a society where wives aren't people. (I think I vaguely mentioned possibly using the decorator pattern, where `Wife` is a decorated `Person`.) – James Kanze Dec 20 '11 at 16:04
1

Smart pointers can help you

using boost::shared_ptr; // or std::shared_ptr, or std::tr1::shared_ptr or something like this
class Person { 

private: 
   shared_ptr<Wife> current_wife; 
   //..... 
}; 

Person::SetCurrentWife (shared_ptr<Wife> new_wife) { 

    current_wife = new_wife; 
} 

And now you should not delete any wife at all.

shared_ptr<Person> some_person ( new Person );
...  
shared_ptr<Wife> wife ( new Wife );  
some_person->SetCurrentWife(wife);  
Alexey Malistov
  • 24,755
  • 13
  • 60
  • 84
  • 3
    This sounds like a recipe for disaster. There are only a very few cases where `shared_ptr` works, and I suspect that this is not one of them. For two reasons: first, it's probable that `Wife` also knows who she's a wife of, so you have a cycle (and you need a cycle, since if something happens to `Wife`, the current spouse must be informed). And second, the lifetime of `Wife` is certainly not dependent on the lifetime of the spouse---if `Wife` dies, she shouldn't be kept alive just because the spouse happens to maintain a pointer to her. – James Kanze Dec 20 '11 at 14:13
  • @JamesKanze that's why there is `boost::weak_ptr` I'd guess. It allows breaking cycles and safe notification of dead objects. – cheind Dec 20 '11 at 14:23
  • @ChristophHeindl So which pointers should be weak, and which ones not? `boost::weak_ptr` is a hack, and not generally usable. – James Kanze Dec 20 '11 at 14:29
  • @JamesKanze that depends on what your application tries to accomplish. One way: maintain `weak_ptr` for inter-person relationships and separately hold a container of `shared_ptr` of all living persons. After all you can always upgrade a `weak_ptr` to `shared_ptr` temporarily if you need to ensure 'alivness' while operating on inter-person relationships. – cheind Dec 20 '11 at 14:42
  • @ChristophHeindl That would, of course, work. Sort of, anyway; the spouse would probably like to know immediately when `Wife` died, for example. And the extra vector seems like added complexity for no real reason. It seems like the design has been twisted just so that you can say you are using a smart pointer. – James Kanze Dec 20 '11 at 14:54
  • @JamesKanze what's your suggesstion? – cheind Dec 20 '11 at 14:58
  • @ChristophHeindl I discussed several in my own answer. – James Kanze Dec 20 '11 at 15:09