views:

193

answers:

5

I am moving to C++ from Java and I am having a lot of trouble understanding the basics of how C++ classes work and best practices for designing them. Specifically I am wondering if I should be using a pointer to my class member in the following case.

I have a custom class Foo which which represents the state of a game on a specific turn, and Foo has a member variable of custom class Bar which represents a logical subset of that game state. For example Foo represents a chess board and Bar represents pieces under attack and their escape moves (not my specific case, but a more universal analogy I think).

I would like to search a sequence of moves by copying Foo and updating the state of the copy accordingly. When I am finished searching that move sequence I will discard that copy and still have my original Foo representing the current game state.

In Foo.h I declare my Foo class and I declare a member variable for it of type Bar:

class Foo {
    Bar b;
public:
    Foo();
    Foo(const Foo& f);
}

But in the implementation of my Foo constructors I am calling the Bar constructor with some arguments specific to the current state which I will know at run time. As far as I understand, this means that a Bar constructor is called twice - once because I wrote "Bar b;" above (which calls the default constructor if I understand correctly), and once because I am writing something like "b = Bar(arg1,arg2,...)" in Foo::Foo() and Foo::Foo(const Foo& f).

If I am trying to make as many copies of Foo per second as possible, this is a problem, right?

I am thinking a simple solution is to declare a pointer to a Bar instead: "Bar *b", which should avoid instantiating b twice. Is this good practice? Does this present any pitfalls I should know about? Is there a better solution? I can't find a specific example to help me (besides lots of warnings against using pointers), and all the information about designing classes is really overwhelming, so any guidance would be greatly appreciated!

EDIT: Yes, I will have all the information necessary to create Bar when I create my Foo. I think everyone inferred this, but to make it clear, I have something more like this already for my default constructor:

Foo(int k=5);

and in Foo.cpp:

Foo::Foo(int k) {
    b = Bar(k);
    ...
}

and then my Foo and its Bar member are updated incrementally as the game state changes.

So calling my custom Bar constructor in my Foo constructor declaration initialization list looks like the best way to do it. Thank you for the answers!

A: 

Yes, you can use a pointer member and instantiate like this: b = new Bar(arg1, arg2, ...). That is what I would do, based on your description. Just remember to delete it (probably in the Foo destructor): delete b; or you will leak it.

If you're creating it in the constructor of Foo, and you know the arguments, you can keep your member as-is and call the constructor explicitly in the initializer list and only do it once: Foo() : b(arg1, arg2, ...) {}. Then you don't have to call delete.

jeffamaphone
-1 for suggesting dynamic memory management, +1 for suggesting initialization list. This makes it zero :)
Tronic
Just trying to illustrate the differences for OP.
jeffamaphone
Yes, appreciated, thank you!
Imran
+2  A: 

Compilers are very good at optimizing away unnecessary copying (the standard allows it to avoid calls to copy constructor even if a custom copycon is defined). Implementing short functions in header files also allows more optimizations, as the compiler can then see the internals of it and possibly avoid some unnecessary processing.

In the case of member variable b, maybe you can use the initialization list to avoid two initializations:

Foo(): b(arg1, arg2) {}

In general, what comes to optimization, first make it work, only then benchmark to see if it needs to be made faster and if so, profile to find out where the bottle neck is.

Tronic
"In general, what comes to optimization, first make it work, only then benchmark to see if it needs to be made faster and if so, profile to find out where the bottle neck is." - Thanks for the reminder! I have learned this the hard way in the past, but I still like to "optimize" at design time if it means using a common pattern instead of doing something the naive way and later realizing that it is slow and no one does it that way anyway! :)
Imran
A: 

if you do use a new bar use a auto_ptr -- as long as bar is only used in foo (otherwise a boost::shared_ptr, but these can be slow).

Now about the default constructor... you can get rid of that by providing:

  1. A initializer list for the Foo constructors -- which will initialize bar.

  2. and secondly a non-default constructor for bar which you use in the initializer list.

However, at this point I feel you are creating a mountain out of a mole-hill. Thinking in this way in Java might save you a horde of CPU-cycles. However in C++ a default constructor doesn't (generally) mean much overhead.

Hassan Syed
He's looking for performance. Heap allocation is likely to kill his performance.
vladr
@vlad that is why I mentioned initialization lists and non-default constructors.
Hassan Syed
@Hassan: Nevertheless, dynamic memory will be the bottleneck with your idea. I'm with Vlad on this one.
sbi
Hassan Syed
@Hassan: Well, there is a `new Bar` in your very first sentence - not accompanied by a strong warning. This might give the wrong impression. It certainly did for me.
sbi
@Hassan, you're right; my initial comment was motivated by seeing your (otherwise perfectly valid) `auto_ptr` remark as a potential encouragement to still use dynamic allocation option which, again, could incur locking (unless linking with a non-MT runtime or with libraries like tcmalloc), thrash your cache, etc.
vladr
+1 for auto_ptr link. Thank you.
Imran
@Imran: Note that `std::auto_ptr<>` has very unusual copy semantics and therefor is rarely ever adequate as a class member.
sbi
@sbi read : "as long as bar is only used in foo". It is a simple template that allows correct RAII as long as you understand the semantics -- if it someone starts copying it around then its the programmers fault.
Hassan Syed
+2  A: 

Ideally you'd have all the information necessary to setup Bar at the time Foo is constructed. The best solution would be something like:

class Foo { 
    Bar b; 
public: 
    Foo() : b() { ... }; 
    Foo(const Foo& f) : b(f.a, f.b) { ... }; 
} 

Read more about constructor initialization lists (which has no direct equivalent in Java.)

Using a pointer pb = new Bar(arg1, arg2) will actually most likely deteriorate your performance since heap allocation (which could involve, among other things, locking) can easily become more expensive than assigning a non-default constructed Bar to a default-constructed Bar (depending, of course, by how complex your Bar::operator= is.)

vladr
Thanks for the tidbit about heap allocation. I learned something new from every answer so far, but I will accept this one for now :)
Imran
how on earth does a new operation incur locking ? The C++ standard makes no presumptions about concurrency issues.
Hassan Syed
@Hassan, your point being? Are you claiming that just because the standard "makes no presumptions about something" that something is nonexistant in said standard implementations? You *may* work around locking penalties if one is *not* linking against a multithreaded runtime, or if one uses `tcmalloc`, but have you also considered data locality and cache performance? I guess not! In all cases heap management is a non-trivial (to various degrees) operation which is best avoided when coding for performance.
vladr
Here, too. Why was this down-voted?
sbi
+1  A: 

[...] in the implementation of my Foo constructors I am calling the Bar constructor with some arguments specific to the current state which I will know at run time. As far as I understand, this means that a Bar constructor is called twice - once because I wrote "Bar b;" above (which calls the default constructor if I understand correctly), and once because I am writing something like "b = Bar(arg1,arg2,...)" in Foo::Foo() and Foo::Foo(const Foo& f).

You got that code wrong. C++ can do better:

When you have a member Bar bar; in the class Foo then that means that every instance of Foo will indeed have an instance of Bar named bar. This bar object is created whenever a Foo object is created - that is, during the construction of that Foo object.

You can control the creation of that bar sub-object of Foo in the initialization list of Foo's constructor:

Foo::Foo(Arg1 arg1, Arg2 arg2) 
  : bar(arg1,arg2)
{
}

That line starting with a colon will tell the compiler which constructor of Bar to invoke when it is creating a Foo object using this specific constructor of Foo. In my example Bar's constructor that takes an Arg1 and an Arg2 is invoked.

If you don't specify a constructor of Bar to use to create the bar sub-object of a Foo object, then the compiler will use Bar's default constructor. (That's the one that doesn't take any arguments.)

If you invoke a compiler generated constructor of Foo (the compiler generates default and copy constructors for you under certain circumstances), then the compiler will pick a corresponding Bar constructor: Foo's default constructor will invoke Bar's default constructor, Foo's copy constructor will invoke Bar's copy constructor. If you want to override these defaults, you have to explicitly write these Foo constructors yourself (instead of relying on the compiler-generated ones) giving them an initialization list where the right Bar constructor is invoked.

So you have to pay for one Bar construction for each Foo construction. Unless several Foo objects can share the same Bar object (using copy-on-write, flyweight or something like this), that's what you have to pay.

(On a side-note: The same goes for assignment. IME, however, it's much rarer that assignment should deviate from the default behavior.)

sbi
Errr I don't think he got that first part wrong. He is rightfully concerend, in his original code, about both `Bar()` and `Bar(arg1,arg2)` being called (as well as `operator=`).
vladr
@Vlad: I'm not sure what you're getting at. I said that he got that code wrong. Maybe I said it in a way so that it wasn't clear? I'll try to be more explicit.
sbi
I just wrote Bar() { cout << "default bar constructor called" } and put a similar cout in my custom Bar constructor called in my Foo constructor and saw that both were called when I ran "Foo f;", which lead to my question. Will this effect go away if I use an initialization list as described?
Imran
@Imran: Yes, certainly. Since you didn't specify a constructor to be called in the initialization list, the compiler put in code to invoke the default constructor. Then you invoke another constructor manually: `Bar(arg1,arg2,...)`. That's the two you saw.
sbi
Would whoever down-voted this please tell me what's wrong with it? Or are you just down-voting every answer with better votes than yours?
sbi