如何在隐式复制构造函数/赋值运算符中抑制成员?

时间:2017-09-28 05:09:28

标签: c++ oop

我理解他隐式复制构造函数/赋值运算符执行源对象的成员方复制。

假设我的班级有1000名成员。我想在进行赋值赋值时禁止其中一个成员(并且默认值适用),其他999个成员仍然使用成员方式拷贝作为隐式赋值运算符。

例如:

class Foo
{
    std::string s1;
    std::string s2;
    std::string s3;
    ....
    std::string s1000;
};

然后我们用一些值填充对象f1:

Foo f1;
f1.s1 = "a";
f1.s2 = "b";
f1.s3 = "c";
...

现在我们将f1(source)复制到f2(目标)

Foo f2 = f1;

如果我想抑制“s2”,我怎样才能达到以下结果?

assert(f2.s1 == "a");
assert(f2.s2 == "");        //default value 
assert(f2.s3 == "c");

据我所知,提供复制构造函数/赋值运算符可以解决这个问题。

class Foo
{
    Foo( const Foo& other)
    {
        s1 = other.s1;
        //s2 = other.s2;    //suppress s2
        s3 = other.s3;
        ...
        s1000 = other.s1000;
    };

    Foo& Foo::operator=( const Foo& other)
    {
        s1 = other.s1;
        //s2 = other.s2;    //suppress s2
        s3 = other.s3;
        ...
        s1000 = other.s1000;

        return *this;
    };

    std::string s1;
    std::string s2;
    std::string s3;
    ....
    std::string s1000;
};

但是,我有1000名成员。我不想实现这么大的功能。

如果我实现这样的功能:

class Foo
{
    Foo( const Foo& other)
    {
        *this = other;
        s2 = "";
    };

    Foo& Foo::operator=( const Foo& other)
    {
        *this = other;
        s2 = "";
        return *this;
    };
}

显然,这是无休止的递归。

这是我目前唯一的选择,即:

Foo f2 = f1;
f2.s2 = "";

但是,假设我的项目中有数千个Foo f2 = f1;语句。找到他们所有人在他们之后追加一行是太难了。

因此,我希望最小的代码更改来自定义对象的成员方式副本。我怎么能这样做?

7 个答案:

答案 0 :(得分:4)

我也同意@gsamaras你必须记住单一责任原则。但与他们的答案不同,我认为你可以吃蛋糕并吃掉它。

让我们来看看你班级的责任。首先,它并不关心如何复制所有数据成员。该责任被委托给每个成员所属的类型。我们应该坚持认为,因为这种认识可以明确谁应该负责。

有问题的数据成员s2std::string。那种类型复制自己。但是,如果我们把它包起来呢?

template<typename T>
class suppress_copies {
  T _mem;
public:
  // Allow changing the held member
  operator T&() { return _mem; }
  T& get()      { return _mem; }

  suppress_copies() = default;
  // But stop automatic copies (potentially even  handles moves)
  suppress_copies(suppress_copies const&) {} // default initialize the member
  suppress_copies& operator=(suppress_copies o) { // Accept default initialized "other" 
    using std::swap;
    swap(_mem, o._mem);
    return *this;
  }
};

这就是它,这是负责抑制副本的类型。只需使用此包装器指定成员:

suppress_copies<std::string> s2;

答案 1 :(得分:2)

你听说过Single responsibility principle吗?您使用的班级数据成员太多而违反了它。大功能和大类是容纳错误,误解和不必要的副作用。试想一下将来维护你代码的人......

因此,不要指望轻易逃脱这一点(我的意思是只搜索和替换你的项目并不那么难)。

  

如何真正解决这个问题?

重构

使用紧凑类的合成,只针对他们真正要做的事情。

答案 2 :(得分:1)

最常用的方法是让需要特殊处理的成员知道如何自己做(如嵌入他们的复制构造函数中),以便保留复制构造函数/赋值运算符聚合总是为默认值。这就是让std::vector拥有自己的复制构造函数而不是让聚合手动处理它们拥有的每个数组的想法。

只要所需的行为足够通用,并且不依赖于聚合的其余部分,这可以起作用;这可能不是你的情况 - 这带来了额外的问题:(1)有一个&#34; autoreset&#34; string(在你的类的上下文中才有意义)作为你的公共接口的一个成员可能是一个坏主意,(2)从STL容器继承是有问题的(虽然在这种情况下它会工作,因为没有人会破坏它是多态的)。

因此,对于委托成员本身不起作用的情况,解决此类问题的唯一方法(保留默认的复制构造函数/赋值运算符,但进行一些自定义处理)我发现不幸的是通过一个辅助结构。把所有&#34;正常&#34;使用默认复制构造函数和赋值运算符将成员转换为FooAux结构。然后Foo将继承此并仅添加需要特殊处理的成员;复制构造函数和赋值运算符会将大部分工作委托给基类,只为额外成员添加自定义处理。

答案 3 :(得分:1)

不幸的是,您不能在同一个类中实现自定义复制行为的同时调用编译器的类的默认复制行为。如果执行自定义复制构造/分配,则必须手动复制999个成员,同时禁止1个成员。

您可以根据赋值运算符实现复制构造函数,反之亦然,以避免复制复制代码。例如:

class Foo {
    ...    
public:
    Foo(const Foo& other) :
        s1(other.s1), 
        //s2(other.s2), //suppress s2
        s3(other.s3),
        ...
        s1000(other.s1000)
    {
    }

    Foo& operator=(const Foo& other) {
        if (&other != this) {
            Foo(other).swap(*this);
        } 
        return *this;
    }

    void swap(Foo &other) {
        std::swap(s1, other.s1);
        std::swap(s2, other.s2);
        std::swap(s3, other.s3);
        ...
        std::swap(s1000, other.s1000);
    }

    ...
};

是的,设置是一项繁琐的工作(但是,开始时有一个包含1000个数据成员的类。考虑将类重构为更小的部分!)。一旦完成,您就不必再担心了。

更简单的解决方案是为s2编写一个单独的类,并禁用其复制数据的能力。例如:

class string_nocopy {
    std::string m_str;

public:
    string_nocopy(const std::string &s = std::string()) : m_str(s) {}

    string_nocopy(const string_nocopy &) : m_str() {}

    string_nocopy& operator=(const std::string &s) {
        m_str = s;
        return *this;
    }

    string_nocopy& operator=(const string_nocopy &other) {
        if (&other != this) m_str = "";
        return *this;
    }

    operator std::string() { return m_str; }

    ...
};

然后您不需要Foo中的自定义复制构造函数或复制赋值运算符,默认值就足够了:

class Foo {
    ...
    std::string s1;
    string_nocopy s2;
    std::string s3;
    ... 
    std::string s1000;
};

答案 4 :(得分:0)

0.正如@gsamaras所说,你可能需要重新分配你的课程。 但如果由于某种原因目前这不是一个选择,那么,有一些技巧可能。

  1. 如果这些成员的类型相同,则将它们存储在一个容器(例如std :: array)中,而不是存储在一千个成员中。

  2. 否则,使用默认分配隐藏内部私有结构内的成员。然后,在外部班级&#39; operator =,首先记住静音成员的当前值,然后分配结构,然后将静音成员恢复到之前的值。

答案 5 :(得分:0)

这实际上比你想象的要容易。

声明一个新的字符串类型,派生自std::string,具有专门的复制行为。然后只需使用外部类中的默认复制/移动行为。

struct copyless_string : std::string
{
        // delegte constructors to base class
        using std::string::string;

        // implement custom copy/assignment    
        copyless_string(copyless_string const& str)
        : std::string()
        {}

        copyless_string& operator=(copyless_string const& str)
        {
                clear();
                return *this;
        }

        // and default move behaviour    
        copyless_string(copyless_string && str) = default;

        copyless_string& operator=(copyless_string && str) = default;
};

struct my_class
{
        std::string s1, s2, s3, s4;
        copyless_string s5;  // <--- won't be copied
        std::string s6, s7, s8, s9, s10;
};

答案 6 :(得分:0)

另一种方式:

将类的非复制部分推迟到基类并单独处理。

然后使用零规则来避免编写任何一般案例复制行为。

#include <string>
#include <cassert>

struct CopylessFoo
{
    CopylessFoo() : s2() {}

    CopylessFoo(CopylessFoo const& other)
    : CopylessFoo()
    {}

    CopylessFoo& operator=(CopylessFoo const& other)
    {
        s2.clear();
        return *this;
    }

    CopylessFoo(CopylessFoo&& other) = default;
    CopylessFoo& operator=(CopylessFoo&& other) = default;

    std::string s2;
};

struct Foo : CopylessFoo
{
    // rule of zero - no copy/assignment necessary

    std::string s1;
    // s2 provided by base class
    std::string s3;
//    ....
    std::string s1000;
};

int main()
{
    Foo f;
    f.s1 = "Hello";
    f.s2 = "world";

    Foo f2 = f;
    assert(f2.s1 == "Hello");
    assert(f2.s2 == "");
}