6

I am aware of the differences between pass by value, reference or pointer in the general case. However, my question is about special case about a container with simple structure.

Assuming this case:

class image{
    image()=default;
    image(image const&)=default;
    ~image()=default;

    int w;
    int h;
    uchar* data;
}

When passing an object of this class what copied are just two integers and pointer not the whole data. In this case is there purpose to passing it by reference ? Or is there a purpose to not passing it by reference?

The thing that triggered this question is that I have read that iterators in C++ were designed to be light and to be passed by value. So, I think that this concept may be applied to classes that represent a container to actual data no a data.

Humam Helfawi
  • 19,566
  • 15
  • 85
  • 160
  • 1
    What is `uchar* data` ? I mean Is it a resource to deep copy and clean ? – Jarod42 Jan 27 '16 at 08:40
  • when copying (not cloning) just the pointer will be copied and both object will share the same actual data – Humam Helfawi Jan 27 '16 at 08:41
  • related: http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – Giorgi Moniava Jan 27 '16 at 08:43
  • @HumamHelfawi that's not how you should write classes, copy should mean copy and move should mean move. Copies can share ownership of resources through stuff like `shared_ptr` but having raw pointers own resources just seems outdated (that doesn't mean there shouldn't be raw pointers, they just shouldn't own resources). – PeterT Jan 27 '16 at 08:43
  • 2
    The default destructor doesn't delete `data`, so apparently the pointer points to some external (to the class) resource. Copying such a pointer can be quite ok. Perhaps `w` and `h` refers to some specific part of the pointed to data, similar to a `string_view`? – Bo Persson Jan 27 '16 at 08:47
  • 3
    and if that class is just a view onto memory managed elsewhere you should probably call it "imageView" to indicate that it merely represents a view onto image data, instead of representing image data itself. – PeterT Jan 27 '16 at 08:47
  • @BoPersson he called that thing a "container". I think we can agree that if the memory is managed elsewhere it's basically just a view into that memory just like iterators basically a view into containers. But people don't usually call iterators containers themselves. – PeterT Jan 27 '16 at 08:53
  • Sorry guys for this rubbish example. i just need to clarify the idea with minimal example. it is not private of course. no memory leaks because I am going to use smart pointers.. everything not related to the problem will be solved in a way or another – Humam Helfawi Jan 27 '16 at 08:54
  • @HumamHelfawi depending on the smart pointer my answer might change though. I mean if you copy it and you have a shared_ptr then you might incur some cost if you call that function a lot and especially when it's in different threads. – PeterT Jan 27 '16 at 08:57

2 Answers2

6

Imho, the best guidelines on how to pass arguments can be found in Herb Sutters excellent talk Back to the Basics! Essentials of Modern C++ Style. In your particular case, passing by value would be the best option, as your struct is cheap to copy.

cpp parameter passing overview.

jupp0r
  • 4,502
  • 1
  • 27
  • 34
  • You could even argue that this struct is also "cheap to move" - especially if it has a managed pointer like `unique_ptr` which is guaranteed to support move properly, and suggest to pass it in by reference. I would think that this particular example is so small that both arguments / decisions are equally valid. – CompuChip Jan 27 '16 at 08:59
  • It isn't just the copying cost that is important; distinct objects get more optimization opportunities because the compiler is allowed to assume that nothing else can point/refer to that object. For example: ` int gi; int val(int i) { gi += i; gi += i; return i; } int ref(const int& i) { gi += i; gi += i; return i; } int main() { gi = 2; std::cout << val(gi) << std::endl; // outputs 2 gi = 2; std::cout << ref(gi) << std::endl; // outputs 8 } ` In ref(), the compiler cannot assume the value of i doesn't change. – Nevin Jan 27 '16 at 15:51
5

With the default copy constructor, any copy will be shallow and will not make a copy of the memory pointed to by data. So when you pass by reference, only the two integers and pointer will be passed, for a total of approximately 12 bytes (depending on your architecture, i.e. pointer size).

That difference is so small, that it does not really matter whether you pass it by value or by reference. The latter may be slightly faster because the pointer can probably always be passed in via a CPU register and the 12 bytes may not, but that is really micro-optimization.

Personally I pass anything but primitive types by (const) reference, by default, unless I have a reason not to (see jupp0r's answer). Major advantage during development is that as the class grows I don't have to worry about when it becomes too big and change all my functions to pass by reference.

As far as the default C++ iterators go: note that they are meant to basically be just pointers. In fact I know that for Microsoft Visual C++ compiling in Release mode with optimizations enabled, iterators for contiguous data structures such as std::vector will reduce to just that. I am not aware if other compilers do this as well, I would think so. However you will notice that if you disable optimizations, then suddenly there is a difference between writing it++ and ++it in loop increments, for example - purely because of the additional copy operation in the former case. So even "cheap to copy" may have an impact if you do it often enough. As usual: when worried about performance, measure and decide based on the numbers.

Community
  • 1
  • 1
CompuChip
  • 9,143
  • 4
  • 24
  • 48
  • Iterators for anything other than contiguous data structures cannot reduce to just pointers, as they have different semantics. – Nevin Jan 27 '16 at 15:35