如何通过多层组件传递对象时确保没有复制

时间:2017-05-29 21:30:25

标签: c++

我有一个带有几层组件的应用程序。假设底部组件是A,并且顶部有组件B,C,D。

假设我在每个组件中都有一个方法,名称相同,例如' getMeSomething',从D调用.D调用C,C调用B,B调用A. 每个组件都有机会在调用其他组件之前做一些事情,并有机会对它得到的东西做一些回报(基于一些参数)。它甚至可以决定返回新的Something对象,而不是调用较低的组件。

所以,例如,当' D'组件调用方法在' C'组件:

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

在' C'组件就像这样处理:

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
   }
}

类似的事情发生在B和A中。

我关心的是按价值返回Something。 Something可能很大,我想确保它可以有效地传递,至少在这些情况下,在特定层中没有必要对这个Something对象进行任何操作(参见' arg == 2&#39的情况) ; 以上)。

例如,如果A返回Something而B返回同一个对象而不触及它,而C也只是将同一个对象返回给D,我想避免复制。我希望Something被移动,而不是被复制。

我如何确定在这种情况下使用移动?我可以吗?我该怎么做才能提供可移动性?

我想我应该将移动构造函数提供给Something类(或确保它可以由编译器自动生成)。我是对的吗?

但是当一个图层影响Something对象时会怎么样。这会影响整个情况,我的意思是Something刚刚传递到下一层的情况吗?

那么复制遗漏呢?编译器会使用这种技术而不是移动吗?这里的规则是什么?

一个更冗长的例子(为简单起见,有3个组件)。

#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);
}

2 个答案:

答案 0 :(得分:0)

首先,总是通过引用返回。已经在评论中了。如果要改变,那就不要使它成为常数。虽然这是糟糕的设计,因为它会伤害封装。移动的东西当然有效,但移动有点听起来像是在这种情况下让对象不一致......但是,是的,取决于案例,不知道你的背景。

那说,还有另一种方法,只需控制Something的复制构造函数。

第一个解决方案:

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

也就是说,只需在打印消息时进行调试即可。当然,这需要您相信您的调试案例以涵盖所有情况,但与您所谈论的设计一样,这似乎是给出的。

第二个解决方案:

Something(const Something& to_copy) = delete;

只需删除复制构造函数即可。也就是说,也许你想要Something的对象一般是可复制的。然后去

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

或者,如果你想保留一个班级,

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

(我省略了可访问性修饰符)

答案 1 :(得分:0)

返回值时,您有几个不同的选项。您拥有的第一个选择是,如果您实际使用返回值,或者您通过引用的参数传递了您修改的参数。下一个选择是C ++为您提供的众多选项之一:

按值:

SomethingBig foo();

根据您的类和编译器的设计,这可能包括也可能不包括复制大量数据。

通过(const)引用:

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

第一个包括思考你的对象所在的位置,即你不能像这样从函数范围返回对象。第二个版本将此责任传递给调用者。

通过指针:

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

指针快速复制,但你需要确保不引入内存泄漏,这对于原始指针来说可能很棘手,所以.......

通过智能指针:

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

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

通过一点点开销,智能指针可确保正确删除对象。这里的第一个版本可能需要一些std::move(),第二个和第四个版本要求调用者首先创建一个(可能是空的)智能指针。如果只有一个所有者,则使用unique_ptr;如果可能有多个,则使用shared_ptr

通过设计合理的课程:

如果以上都不能满足您的需求,您需要正确设计您的课程。 通过实施(或删除)复制/移动构造函数/作业以及可能的“外包”和#39; (智能)指针后面的大数据块您可以高度控制何时实际复制内容。如果你另外实施move-constructors / assigments SomethingBig(SomethingBig&&);&amp; SomethingBig& operator=(SomethingBig&&);您创建了一个无法复制但移动(因此使用std::move()返回)的类,而不实际复制所有数据。