【问题标题】:Named Parameter Idiom and non-copyable class命名参数习语和不可复制的类
【发布时间】:2020-03-18 10:35:39
【问题描述】:

我正在实现一个使用命名参数惯用语来初始化其成员的类:

class Person
{
    public:
        Person & first_name(std::string n) { m_first_name = n; return *this; }
        Person & last_name(std::string l) { m_last_name = l; return *this; }
        Person & age(int a) { m_age = a; return *this; }

        std::string get_first_name() const { return m_first_name; }
        std::string get_last_name() const { return m_last_name; }
        int get_age() const { return m_age; }

    private:
        std::string m_first_name;
        std::string m_last_name;
        int m_age;
};

到目前为止,一切正常。

#include <string>
#include <iostream>

class Person
{
    public:
        Person & first_name(std::string n) { m_first_name = n; return *this; }
        Person & last_name(std::string l) { m_last_name = l; return *this; }
        Person & age(int a) { m_age = a; return *this; }

        std::string get_first_name() const { return m_first_name; }
        std::string get_last_name() const { return m_last_name; }
        int get_age() const { return m_age; }

    private:
        std::string m_first_name;
        std::string m_last_name;
        int m_age;
};

void print(Person const & p)
{
    std::cout << p.get_last_name() << ", " << p.get_first_name() << ", " << p.get_age() << '\n';
}

int main()
{
    auto p1 = Person().first_name("Joe").age(20);
    auto p2 = Person().last_name("Kane").age(50);

    print(p1);
    print(p2);
}

以上代码输出

, Joe, 20
Kane, , 50

正如预期的那样。

但是,一旦我使我的类不可复制(在我的情况下,通过添加 std::unique_ptr 成员),上面的代码将无法编译

#include <string>
#include <memory>
#include <iostream>

class Person
{
    public:
        Person & first_name(std::string n) { m_first_name = n; return *this; }
        Person & last_name(std::string l) { m_last_name = l; return *this; }
        Person & age(int a) { m_age = a; return *this; }

        std::string get_first_name() const { return m_first_name; }
        std::string get_last_name() const { return m_last_name; }
        int get_age() const { return m_age; }

    private:
        std::string m_first_name;
        std::string m_last_name;
        int m_age;
        std::unique_ptr<int> m_ptr; // added member
};

void print(Person const & p)
{
    std::cout << p.get_last_name() << ", " << p.get_first_name() << ", " << p.get_age() << '\n';
}

int main()
{
    auto p1 = Person().first_name("Joe").age(20);
    auto p2 = Person().last_name("Kane").age(50);

    print(p1);
    print(p2);
}

G++ 出现以下错误:

prog.cc: In function 'int main()':
prog.cc:30:48: error: use of deleted function 'Person::Person(const Person&)'
   30 |     auto p1 = Person().first_name("Joe").age(20);
      |                                                ^
prog.cc:5:7: note: 'Person::Person(const Person&)' is implicitly deleted because the default definition would be ill-formed:
    5 | class Person
      |       ^~~~~~
prog.cc:5:7: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
In file included from /opt/wandbox/gcc-head/include/c++/10.0.0/memory:82,
                 from prog.cc:2:
/opt/wandbox/gcc-head/include/c++/10.0.0/bits/unique_ptr.h:456:7: note: declared here
  456 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
prog.cc:31:48: error: use of deleted function 'Person::Person(const Person&)'
   31 |     auto p2 = Person().last_name("Kane").age(50);
      |                

我通过返回右值解决了这个问题

class Person
{
    public:
        Person && first_name(std::string n) { m_first_name = n; return std::move(*this); }
        Person && last_name(std::string l) { m_last_name = l; return std::move(*this); }
        Person && age(int a) { m_age = a; return std::move(*this); }

        // ...
};

这是正确的解决方法吗?有什么我应该考虑的陷阱吗?

【问题讨论】:

    标签: c++17 named-parameters


    【解决方案1】:

    问题在于p1p2的声明

    //      V <-- first copy
    auto p1 = Person().first_name("Joe").age(20);
    auto p2 = Person().last_name("Kane").age(50);
    //      ^ <-- second copy
    

    您有两个 Person 副本。

    你可以解决移动物体的问题

    auto p1 = std::move(Person().first_name("Joe").age(20));
    auto p2 = std::move(Person().last_name("Kane").age(50));
    

    或在之前声明p1p2

    Person p1;
    Person p2;
    
    p1.first_name("Joe").age(20);
    p2.last_name("Kane").age(50);
    

    您的解决方案(从 set 方法返回 Person &amp;&amp;)与第一个解决方案(使用 std::move())产生相同的效果,但非常具有侵入性:每次设置某些内容时,您都会“移动”对象。

    【讨论】:

    • 我意识到“侵入性”,我可以忍受它。但是,我希望客户端代码尽可能简单(因此 auto p1 = ... 而不是 auto const &amp; p1 = ... 等),并且所有使其工作的负担都在课堂上。
    • @KrzysiekKarbowiak - 如果你想全部都在课堂上,你可以添加一个简单地返回Person &amp;&amp;move()方法,所以auto p1 = Person().first_name("Joe").age(20).move()
    猜你喜欢
    • 2019-03-25
    • 2010-12-31
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 2012-06-22
    • 2012-02-09
    • 2019-03-20
    相关资源
    最近更新 更多