-1

I have an application with a few layers of components. Say that the bottom component is A and there are components B, C, D on top of it.

Say I have a method in every component, named the same, for example 'getMeSomething', which is called from D. D calls C, C calls B, B calls A. Every component has a chance to do something before calling other component, and a chance to do something with the thing that it gets in return (based on some argument). It can even decide to return new Something object instead of calling lower components.

So, for example, when 'D' component calls method in 'C' component by:

// code from D class
Something s = c.getMeSomething(1)

In the 'C' component it is handled like this:

Something C::getMeSomething(int arg)
{
   if (arg == 1)
   {
      Log ("Hey, I've got 1");
      Something sth = b.getMeSomething();
      sth.remove(arg); // or whatever method on sth
   }
   else if (arg == 2)
   {
      return b.getMeSomething();
   }
   else
   {
      return Something(123); // whatever here
   }
}

Similar things happen in B and in A.

What I am concerned about is returning the Something by value. The Something can be big and I would like to make sure it can be efficiently passed, at least in these cases when nothing must be done in particular layer about this Something object (see the case when 'arg == 2' above).

For example, in case when A returns Something and B returns the same object without touching it, and C also just returns the same object to D, I would like to avoid copying. I would like the Something be moved, not copied.

How can I be sure that move is used in this case? Can I? What should I do to provide moveability?

I guess I should provide the move constructor to Something class (or make sure it can be auto-generated by compiler). Am I right?

But what about the cases when one layer affects the Something object. Will that affect the whole situation, I mean the cases when the Something is just passed on to next layer?

And what about copy-elission? Will the compiler use this techique instead of moving? What are the rules here?

A more verbous example (with 3 components, for simplicity).

#include <iostream>
#include <cassert>

struct Sth 
{
    int x;
};

struct A 
{
    Sth get(int i) { return Sth{333}; }
};

struct B
{
    A a;
    Sth get(int i) 
    {
        if (i == 1) 
           return a.get(i); 
        else
        {
            Sth s = a.get(i);
            s.x = 444;
            return s;
        }
    }
};

struct C
{
    B b;
    Sth get(int i) { return b.get(i); }
};

int main()
{
    C c;
    Sth s1 = c.get(1);
    assert (s1.x == 333);
    Sth s2 = c.get(2);
    assert (s2.x == 444);
}
YotKay
  • 1,127
  • 1
  • 9
  • 25

2 Answers2

1

When returning values, you have a few different options. The first choice you have is, if you actually use the return value or if you pass a parameter by reference which you modify. The next choice is one of the many options C++ offers you:

By value:

SomethingBig foo();

Depending on the design of your class and your compiler, this might or might not include copying a lot of data.

By (const) reference:

SomethingBig& foo();
void foo(SomethingBig&)

The first includes thinking about where your object lives, i.e. you can't return objects from the functions scope like this. The second version passes this responsibility to the caller.

By pointer:

SomethingBig* foo();
void foo(SomethingBig*);

Pointers are fast to copy, but you need to make sure not to introduce a memory leak, which could be tricky with raw pointers, so....

By smart-pointers:

std::unique_ptr<SomethingBig> foo();
void foo(std::unique_ptr<SomethingBig>&);

std::shared_ptr<SomethingBig> foo();
void foo(std::shared_ptr<SomethingBig>&);

With a tiny bit of overhead, smart-pointers make sure that the objects are properly deleted. The first version here probably requires a few std::move(), the second and fourth version require the caller to first create a (possibly empty) smart pointer. Use unique_ptr if theres always only one owner and shared_ptr if there could be more than one.

By a properly designed class:

If none of the above fits your need right away, you need to design your class properly. By implementing (or deleting) copy/move-constructor/assignment and maybe 'outsourcing' large data chunks behind a (smart-)pointer you have a high level of control about when to actually copy what. Aziuth already pointed out how to prevent copies, if you additionally implement move-constructors/assigments SomethingBig(SomethingBig&&); & SomethingBig& operator=(SomethingBig&&); you create a class which can not be copied, but moved (and therefore returned using std::move()) without actually copying all of your data.

Anedar
  • 4,235
  • 1
  • 23
  • 41
  • Thank you. I think my case is that I want to have a new object (but not heap-allocated) be returned from the lowest component (i.e. I don't want to provide an object from top component to be later returned by reference). – YotKay May 29 '17 at 22:22
  • Ok, so I want moving and need to provide move constructor. That I know. But can I rely on compiler to do it for me? Or can I rely on compiler to elide the copy? That is the question. – YotKay May 29 '17 at 22:31
  • I'm not sure if the compiler has to use the move constructor, but a good compiler will. If in doubt, use `std::move()` or delete the copy-constructor. In the latter case, the move constructor is the only option for the compiler besides creating the object in place, in which case theres no copy or move at all. – Anedar May 29 '17 at 22:34
  • For example I added move constructor to the Sth class in my example, with printout in it. I saw the printout once. Adding move ctor prohibits copying implicitly, so I was sure that no copy is made. But when I removed modification of the Sth object in B class, the printout from move ctor disappeared, thus there is a different technique used, i.e. copy elision. – YotKay May 29 '17 at 22:36
0

First of all, there is always return by reference. Was already in the comments. If it is to be changed, then don't make it const. Although that is bad design since it hurts encapsulation. Moving things of course works, but moving somewhat sounds like leaving objects inconsistent in such cases... but yeah, depends on the case, don't know your context.

That said, there is another way, simply control the copy constructor of Something.

First solution:

Something(const Something& to_copy){
    #if DEBUG
        cout << "was copied!" << endl;
    #endif
    (more stuff)
}

That is, simply check it when you are debuggin by printing a message. Of course this requires you to trust in your debug cases to cover all cases, but as with the design you talk about, this seems to be given.

Second solution:

Something(const Something& to_copy) = delete;

Simply delete the copy constructor. That said, maybe you want objects of Something in general to be copyable. Then go either with

class SomethingUncopyable : public Something {
    SomethingUncopyable (const SomethingUncopyable & to_copy) = delete;
    ...
}

or, if you want to keep one class,

class Something {
    bool _copyable;
    Something( arguments, const bool copyable = true) : _copyable(copyable) {
        ...
    }
    Something(const Something& to_copy){
        assert(_copyable)
        ...
    }
}

(I omitted the accessability modifiers)

Aziuth
  • 3,652
  • 3
  • 18
  • 36
  • But I want in the D (top) component to get a new object of Something. How can I return object by reference when the object is to be created by A (the lowest) component? – YotKay May 29 '17 at 22:02
  • Maybe it wouldn't hurt if you give a context. Right now, it sounds as if you do want to move indeed. That said, since you really don't want a copy to happen, I assume that the object is big... and a big data flow between classes might be an indicator for bad design. Another way might be that you simply pass a shared_ptr. But again, the context is important. – Aziuth May 29 '17 at 22:06
  • But the allocation in heap also costs, using shared_ptr is not free. I would like to stick to automatic objects on stack. For now let's just say I have small or at most medium-sized objects, so it is ok to copy them, but I still would like to avoid copying and use moving (or copy elision). – YotKay May 29 '17 at 22:11
  • @YotKay Again: Please give context. That said, if you want to use moving, use moving. Use one of the concepts in my answer to reveal or hinder copying. – Aziuth May 29 '17 at 23:14
  • @YotKay By the way, how many of those objects do you have that the costs of shared_ptr are relevant? – Aziuth May 29 '17 at 23:17