开发者

C++: How to improve performance of custom class that will be copied often?

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开发者_JS百科 list looks like the best way to do it. Thank you for the answers!


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.)


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.


[...] 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.)


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜