1

To give a concrete context, I am talking about an OO designed system with quite some complexity, e.g. over 100 classes. This is a typical project like most in my experience, current one and where my confusions come from.

I am from a Java/C# background. Honestly, I admit those languages brainwashed me. So I get confusions about using copy, reference or pointer. I did great in C and Computer Architecture course back in college, have read books like c++ primer, effective c++. I have no problem understanding copy/reference/pointer individually, but not quite fluent and confident to make proper use of them in practice.

Here are two cases, for simplicity, just ignore other concepts like move, const, overloads, inheritance, smart pointer etc. Most of my problems are about copy or reference? copy or pointer?

  1. As class member field. In Java or C#, things are more like pointer: A and B can have the same/different C member, If you have same C in A and B, when you change C in A, C in B also get changed. It is common that one object shared by more than one objects. Is pointer a must in this case? But using pointers introduce much more complexity in C++ code, even with smart pointer.

  2. As class method return. Looks like reference is preferred. It avoids copy and the behavior is what I am used to: return Foo& from a method while the actual Foo is in one class and possibly shared among classes. Whatever made to that Foo& later on apply to its owner and other users. But how do I guarantee the reference is valid? It is unlike Java/C# with GC, as long as it is been used, it is there. Is it possible I get an Foo& to a Bar's member, then lots of things may happen to that Foo&, through class or functions but some how the Bar get destroyed: end of scope on stack or deleted on heap? C++ object lifetime are deterministic, ironically, that makes me uncertain about whether a reference's object is still alive!

To sum up, the mix of possible unnecessary copies, pointer's memory management and uncertainty of object lifetime is my trouble. Are there any guidelines and good practices for my problem?

Ryan
  • 1,963
  • 3
  • 25
  • 35
  • I am pleased that you got that of your chest – Ed Heal Jan 25 '14 at 00:17
  • I have to speak out. It makes me upset and depressed for the last few days, since when I started to work on my c++ project! – Ryan Jan 25 '14 at 00:22
  • You need to start thinking in terms of *object ownership* (what object is owned by which other object) and once you get it nailed down, most problems will just disappear. – n. m. could be an AI Jan 25 '14 at 00:28
  • @n.m. You are probably right. But in real cases ownership may not quite clear, or something nobody really owns it but uses it. – Ryan Jan 25 '14 at 00:37
  • Some objects are owned jointly (shared), nothing wrong with that, but you *need* to know which ones, and why. – n. m. could be an AI Jan 25 '14 at 01:35

1 Answers1

2

Basic cases

  • shared large member data -> std::shared_ptr
  • scoped large member data -> std::unique_ptr
  • shared/scoped small member data -> value
  • required large input arguments -> const reference
  • required small argument -> value
  • required input/output arguments -> reference
  • optional argument -> pointer / smart pointer
  • large argument with shared ownership -> std::shared_ptr const&
  • large argument with ownership transfer -> std::unique_ptr&&
  • small return value -> value
  • large return value strictly not going out of scope never -> const reference
  • large return value not going out of scope soon where performance penalty of returning by value is not tolerable and wrapping to a smart pointer is not practical -> const reference
  • large return value with shared ownership -> std::shared_ptr
  • large return value with ownership transfer -> std::unique_ptr

Somewhat less trivial cases (addition)

  • copy on write container -> std::shared_ptr
  • returning large shared object without ownership transfer -> std::weak_ptr
  • returning large value when a copy is required at destination and the type has a copy constructor taking rvalue reference as an argument -> value
bobah
  • 18,364
  • 2
  • 37
  • 70
  • scoped large member data -> std::unique_ptr. Why prefer this over just a member object? – Ryan Jan 25 '14 at 00:26
  • function large return value strictly not going out of scope never -> const reference. How do I know it is never? – Ryan Jan 25 '14 at 00:27
  • Actually: Almost every data member: value. Not a reference, not a pointer, not a smart pointer. Shared data member? Almost always a reference or a raw pointer (no ownership!). Ownership required but sometimes empty? *Only here* `std::unique_ptr` (never empty: value). Ownership totally unclear? Redesign. Too lazy? `std::shared_ptr`. – Konrad Rudolph Jan 25 '14 at 00:27
  • scoped large member data - it will most probably be allocated on heap, everything allocated on heap is probably better to wrap into a smart pointer to mechanically prevent memory leaks – bobah Jan 25 '14 at 00:28
  • @bobah You basic rules are clear and helpful! – Ryan Jan 25 '14 at 00:29
  • strictly not going out of scope never - logger and alike – bobah Jan 25 '14 at 00:30
  • @KonradRudolph Can you explain more about why shared member always raw pointer but not shared_ptr, unless I am too lazy? – Ryan Jan 25 '14 at 00:30
  • @KonradRudolph - I too struggle to translate your post:) You can call it laziness but it is SOO much easier to have compiler release memory and get me green light in Valgrind – bobah Jan 25 '14 at 00:34
  • Not always true - you're not taking into account move semantics, esp. when it comes to passing large objects as arguments. If you're modifying the object inside the function, it's better to pass by value and bypass the additional copy which you'd make if you pass by const reference. Also, returning by value is doable due to RVO. – Luchian Grigore Jan 25 '14 at 00:35
  • From OO modeling point of view, return by value through moving and copying are no quite different, giving move and copy ctors are consistent. Only difference is performance, am I right? – Ryan Jan 25 '14 at 00:41
  • @Ryan `shared_ptr` implies ownership. If your object doesn’t own the other object then it *must not use* a `shared_ptr`. In most scenarios there should be exactly *one* owner of an object, and that owner will hold a *value*, not a pointer, in most cases. – Konrad Rudolph Jan 25 '14 at 00:42
  • @LuchianGrigore - If you return member string field by value if will be by an order of magnitude slower than the same by const reference, you need to decide whether you want to pay such penalty for the perfect safety and clarity of returning by value. I love C++ for the freedom it leaves to the programmer, first of all. – bobah Jan 25 '14 at 00:46
  • @KonradRudolph I get part of it, if Foo does not own Bar but want to use bar as member, it can have a raw Bar*. Foo won't delete Bar* in destructor since it is not the owner. But could it be a problem when Foo want to use Bar but it is deleted by the owner. pointer check can avoid carshes, but cannot let Foo use that deleted Bar. – Ryan Jan 25 '14 at 00:47
  • @bobah really? Did you profile? Can you back up that statement with some numbers? All decent compilers will perform return value optimization and skip the extra copy. Plus, returning by reference is not an option all the time. – Luchian Grigore Jan 25 '14 at 00:51
  • @L.G. - I DID profile, did you? How would optimizing compiler optimize out a copy if the copy is done by the method code which can be in a separate library? Write a probe object that logs from ctors/dtors and try. – bobah Jan 25 '14 at 00:55
  • See http://ideone.com/UfxSQ2 - and look up return value optimization. It's a key concept in modern C++ that you're unaware of. Read this post - http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization – Luchian Grigore Jan 25 '14 at 01:17
  • Also read 12.8 Copying and moving class objects [class.copy] – Luchian Grigore Jan 25 '14 at 01:17
  • @LuchianGrigore - http://ideone.com/mndNe0; which of us is unaware of RVO is a good question. You should understand the difference between what optimizations are possible in C++ compilers and which are only possible in JIT ones. – bobah Jan 25 '14 at 09:15