C++14:如何设计二传手?
C++14: How to design setters?
假设我有一个相当大的 class A
,其中有另一个 class B
:
的成员
class A {
various large data members here
}
class B {
public:
setA( .... );
private:
A a;
}
为 B
中的 A
成员编写 setter 的最佳方法是什么(在速度和代码整洁度方面)?
第一个选项是典型的 C++98 风格:
void setA(const A& a) { this.a = a; }
并像
一样使用它
B b;
A a;
b.setA(a);
这至少需要一份副本,这可能会很昂贵。对于大对象更有效的可能是指针:
class B {
public:
setA( .... );
private:
A *a;
}
setA(A *a) { this->a = a; }
和
B b;
A *a = new A();
b.setA(a);
这伴随着指针的所有麻烦,必须编写自定义析构函数等。在 C++14 中,可以使用 std::uniuqe_ptr,但这仍然比简单地拥有一个非指针成员要干净得多在 B
.
中的 A
在 C++14 中,可能有使用 rvalues
的选项,并通过简单的按值调用移动语义,即
void setA(A a) { this.a = std::move(a); }
并这样称呼它:
B b;
A a;
b.setA(std::move(a)); // if we don't need a anymore
这很好,很干净,应该很快,但是:
- 我必须为 class A 实现移动语义。这很烦人
- 如果我依赖于自动生成的移动语义,那么检查对象是否被移动似乎是非常重要的(如果可能的话):为了检查是否调用了移动或复制构造函数,我有实现复制构造函数。然后根据 C++14 规范将不会生成移动构造函数和赋值。如果我做一些花哨的事情,比如将复制构造函数设置为删除,我的代码将无法编译(因为某些代码部分明确要求复制
A
)。
Herb Sutter 在他关于回到基础知识的幻灯片中的论点!你通常应该喜欢的现代 C++ 风格的精要
void setA(const A& a) { this.a = a; }
但他在演讲中谈到了一个相当小的成员(字符串),因此对于更大、更复杂的对象,这可能不适用...
正确的做法是什么(在 Java 中,这很简单...)
您的对象在构建后应该可以使用了。为什么在构建class B 时不通过class A?
#include <memory>
class A
{
public:
A(int a) : a{ a } {}
private:
int a;
};
class B
{
public:
B(int b, A a) : b{ b }, a{ std::make_unique<A>(a) } {}
private:
int b;
std::unique_ptr<A> a;
};
int main()
{
B b(int{ 4 }, A{ 1 });
}
你可以有两个 setter,一个用于右值引用,另一个用于 const&,如下所示:
void setA(const A& a) { a_ = a; }
void setA(A&& a) { a_ = std::move(a); }
或者,如果您不打算将 setA 与任何其他类型一起使用,您可以使用通用引用:
template<typename T>
void setA(T&& a_) { a_ = std::forward<T>(a); }
但是既然你已经允许设置这个值,你不妨考虑一下public,这样你就完全不用担心设置器了。
假设我有一个相当大的 class A
,其中有另一个 class B
:
class A {
various large data members here
}
class B {
public:
setA( .... );
private:
A a;
}
为 B
中的 A
成员编写 setter 的最佳方法是什么(在速度和代码整洁度方面)?
第一个选项是典型的 C++98 风格:
void setA(const A& a) { this.a = a; }
并像
一样使用它B b;
A a;
b.setA(a);
这至少需要一份副本,这可能会很昂贵。对于大对象更有效的可能是指针:
class B {
public:
setA( .... );
private:
A *a;
}
setA(A *a) { this->a = a; }
和
B b;
A *a = new A();
b.setA(a);
这伴随着指针的所有麻烦,必须编写自定义析构函数等。在 C++14 中,可以使用 std::uniuqe_ptr,但这仍然比简单地拥有一个非指针成员要干净得多在 B
.
A
在 C++14 中,可能有使用 rvalues
的选项,并通过简单的按值调用移动语义,即
void setA(A a) { this.a = std::move(a); }
并这样称呼它:
B b;
A a;
b.setA(std::move(a)); // if we don't need a anymore
这很好,很干净,应该很快,但是:
- 我必须为 class A 实现移动语义。这很烦人
- 如果我依赖于自动生成的移动语义,那么检查对象是否被移动似乎是非常重要的(如果可能的话):为了检查是否调用了移动或复制构造函数,我有实现复制构造函数。然后根据 C++14 规范将不会生成移动构造函数和赋值。如果我做一些花哨的事情,比如将复制构造函数设置为删除,我的代码将无法编译(因为某些代码部分明确要求复制
A
)。
Herb Sutter 在他关于回到基础知识的幻灯片中的论点!你通常应该喜欢的现代 C++ 风格的精要
void setA(const A& a) { this.a = a; }
但他在演讲中谈到了一个相当小的成员(字符串),因此对于更大、更复杂的对象,这可能不适用...
正确的做法是什么(在 Java 中,这很简单...)
您的对象在构建后应该可以使用了。为什么在构建class B 时不通过class A?
#include <memory>
class A
{
public:
A(int a) : a{ a } {}
private:
int a;
};
class B
{
public:
B(int b, A a) : b{ b }, a{ std::make_unique<A>(a) } {}
private:
int b;
std::unique_ptr<A> a;
};
int main()
{
B b(int{ 4 }, A{ 1 });
}
你可以有两个 setter,一个用于右值引用,另一个用于 const&,如下所示:
void setA(const A& a) { a_ = a; }
void setA(A&& a) { a_ = std::move(a); }
或者,如果您不打算将 setA 与任何其他类型一起使用,您可以使用通用引用:
template<typename T>
void setA(T&& a_) { a_ = std::forward<T>(a); }
但是既然你已经允许设置这个值,你不妨考虑一下public,这样你就完全不用担心设置器了。