C ++: How to improve the performance of a custom class that will be copied frequently?

I am moving on to C ++ with Java, and I am having trouble understanding the basics of C ++ classes and the best methods for designing them. In particular, I am wondering if I should use a pointer to my class member in the following case.

I have a custom class Foo that represents the state of a game at a particular turn, and Foo has a member variable in the user line of the class, which is a logical subset of this state of the game. For example, Foo represents a chessboard, and Bar represents objects under attack and their shoots (not my specific case, but a more universal analogy, I think).

I would like to find the sequence of steps by copying Foo and accordingly updating the state of the copy. When I finish searching for this sequence of movements, I will drop this copy and still have my original Foo representing the current state of the game.

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

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

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

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

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

EDIT: Yes, I will have all the information needed to create a Bar when I create my Foo. I think everyone brought this out, but to be clear, I have something similar 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 his Bar member are updated gradually as the state of the game changes.

So calling my custom constructor bar on my list of constructor constructor foo Looks like the best way to do this. Thanks for answers!

+4
source share
5 answers

Ideally, you will have all the information you need to configure Bar at the time you create Foo . A better solution would be something like:

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

Learn more about constructor initialization lists (which does not have a direct equivalent in Java.)

Using the pb = new Bar(arg1, arg2) pointer pb = new Bar(arg1, arg2) will in fact most likely spoil the performance from the moment of allocation of the heap (which may include, among other things, locking) can easily become more expensive than the appointment of a non-constructor by Bar to the default Bar (depending, of course, on how complex your Bar::operator= .)

+2
source

Compilers very well optimize unnecessary copying (the standard avoids calls to copy the constructor, even if a specific custom copycon is defined). The implementation of short functions in the header files also allows for more optimization, since the compiler can see its internals and, possibly, avoid unnecessary processing.

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

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

In general, what happens with optimization, first do it, only then to check whether it needs to be done faster, and if so, then a profile to find out where the bottle’s throat is.

+2
source

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

You have the wrong code. C ++ can do better:

When you have a Bar bar; member Bar bar; in the Foo class, this means that every instance of Foo really has a Bar instance called Bar . This Bar object is created whenever a Foo object is created, that is, during the construction of this Foo object.

You can control the creation of this Bar Foo sub-object in the initialization list of the Foo constructor:

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

This line, starting with a colon, tells the compiler which Bar constructor is called when the Foo object is created using this particular Foo constructor. In my example, the constructor is Bar , which accepts Arg1 and Arg2 .

If you do not specify the Bar constructor that will be used to create the Bar sub-object of the Foo object, then the compiler will use the default Bar constructor. (This is one that takes no arguments.)

If you call the Foo constructor created by the compiler (the compiler creates default constructors for you and copies for you under certain circumstances), then the compiler will choose the appropriate Bar constructor: Foo default constructor will call the Bar default constructor, the Foo constructor will call the Bar copy constructor. If you want to override these defaults, you must explicitly write these Foo constructors yourself (instead of relying on the ones generated by the compiler), providing them with an initialization list where the right Bar constructor is called.

Therefore, you need to pay for one Bar construct for each Foo construct. If multiple Foo objects cannot use the same Bar object (using copy-on-write, flyweight, or something like that), this is what you have to pay.

(On a side note: The same goes for the assignment. IME, however, is much less likely that the assignment should deviate from the default behavior.)

+1
source

Yes, you can use a pointer element and create an instance as follows: b = new Bar(arg1, arg2, ...) . This is what I would do based on your description. Just remember to delete it (possibly in the Foo destructor): delete b; or you miss it.

If you create it in the Foo constructor and know the arguments, you can save your member as is and call the constructor explicitly in the initializer list and do it once: Foo() : b(arg1, arg2, ...) {} . Then you do not need to call delete.

0
source

if you use new bar , use auto_ptr - while the bar is used only in foo (otherwise a boost :: shared_ptr, but they can be slow).

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

  • A list of initializers for the Foo constructors that will initialize the panel.

  • and secondly, the non-standard constructor for the panel, which you use in the list of initializers.

However, at this moment I feel that you are creating a mountain from a mole hill. Such thinking in Java can save you a horde of CPU cycles. However, in C ++, the default constructor does not (usually) mean a lot of overhead.

0
source

Source: https://habr.com/ru/post/1302677/


All Articles