简体   繁体   English

在2018年使用C ++ 11及更高版本时,helper init()函数被认为是不好的形式吗?

[英]In 2018 with C++11 and higher, are helper init() functions considered bad form?

Pre C++11 you had no non-static member initializion nor did you have construction delegation, so people often used private helper functions to help with initializtion to reduce code replication. 在C ++ 11之前,您没有非静态成员初始化,也没有构造委派,因此人们经常使用私有帮助函数来帮助初始化以减少代码复制。

Is this good code in 2018? 这是2018年的好代码吗?

class A  {
  int a1 = 0;
  double a2 = 0.0;
  string a3 = "";
  unique_ptr<DatabaseHandle> upDBHandle;

  void init(){
      upDBHandle = open_database(a1, a2, a3);
  }

public:
    A() { init(); }
    explicit A(int i):a1(i) {  init(); }
    explicit A(double d):a2(d) {  init(); }
    explicit A(std::string s):a3(std::move(s)) {  init(); } 
    A(int i, double d, std::string s) : a1(i), a2(d), a3(std::move(s)) { init(); }
};

How can this code be improved? 如何改进此代码?

In my opinion, your code is fine. 在我看来,你的代码很好。 I try to avoid relying on subtle effects such as the member initialization order in constructor initialization lists. 我尽量避免依赖于微妙的效果,例如构造函数初始化列表中的成员初始化顺序。 It violates DRY - you need to repeatedly use the same order: In the class body when declaring the members, and in the constructor initialization list. 它违反了DRY - 你需要重复使用相同的顺序:在类体中声明成员时,以及在构造函数初始化列表中。 As soon as time goes by and the class becomes bigger, and you move the constructors into the .cpp file, things start getting more confusing. 一旦时间流逝并且类变大,并且将构造函数移动到.cpp文件中,事情就会变得更加混乱。 Therefore, I put things that require access to other members into init functions. 因此,我将需要访问其他成员的内容放入init函数中。

If the member is const , you can't do this. 如果成员是const ,则不能这样做。 But then again, as the class author you can decide which member is const and which is not. 但话说回来,作为班级作者,你可以决定哪个成员是const,哪个成员不是。 Note that this is not to be confused with the anti-pattern of "construct, then init", because here the init happens within the constructor, and this is invisible to class users. 请注意,这不应与“construct,then init”的反模式混淆,因为init在构造函数中发生,这对类用户是不可见的。

If you still mislike the use of init , I would advice against putting the call into the constructor initialization list. 如果您仍然不喜欢使用init ,我建议不要将调用放入构造函数初始化列表中。 Perhaps for me an acceptable midway is to put it into the in-class initializer, and remove all calls from the constructors. 也许对我来说,可接受的中途是将它放入类内初始化器中,并从构造函数中删除所有调用。

class A  {
  int a1 = 0;
  double a2 = 0.0;
  string a3 = "";
  unique_ptr<DatabaseHandle> upDBHandle = open_database(a1, a2, a3);

  // ...

C++ just isn't very good at dealing with multiple defaults. C ++在处理多个默认值时并不是很擅长。 So doing this nicely is not going to be easy. 所以这样做很好并不容易。 There are different things you can do, but all of them have different trade-offs (eg scattering defaults around). 您可以做不同的事情,但所有这些都有不同的权衡(例如散布默认值)。

IMHO, the nicest solution that can be arrived at here, is one that isn't legal C++ (yet), but is a highly supported extension: designated initializers. 恕我直言,可以在这里得到的最好的解决方案,是一个非合法的C ++(尚未),但是是一个高度支持的扩展:指定的初始化器。

class A  {
  struct Params {
      int a1 = 0;
      double a2 = 0.0;
      string a3 = "";
  };
  Params p;
  unique_ptr<DatabaseHandle> upDBHandle;

public:
    explicit A(Params p_arg) 
      : p(std::move(p_arg))
      , upDBHandle(open_database(p.a1, p.a2, p.a3) { }
};

A a({});  // uses all defaults
A a2({.a2 = 0.5});  // specifies a2 but leaves a1 and a3 at default
A a3({.a1 = 2, .a2=3.5, .a3 = "hello"});  //  specify all

You could use the fact that, if a member is not initialized in a constructor's member initialization list, the default member initializer is executed. 您可以使用以下事实:如果成员未在构造函数的成员初始化列表中初始化,则执行默认成员初始化程序。 Moreover each member initialization is a full expression and the member initializations are always executed in the order of their declarations inside the class: 此外,每个成员初始化都是一个完整的表达式,成员初始化总是按照它们在类中的声明顺序执行:

class A  {
  int a1 = 0;
  double a2 = 0.0;
  string a3 = "";
  unique_ptr<DatabaseHandle> upDBHandle = open_database(a1,a2,a3);
  //a1,a2 and a3 initializations are sequenced before
  //upDBHandle initialization.
public:
  //all these constructors will implicitly invoke upDBHandle's default initializer 
  //after a1,a2 and a3 inialization has completed.
  A() { }
  explicit A(int i):a1(i) {  }
  explicit A(double d):a2(d) {  }
  A(int i, double d, std::string s) : a1(i), a2(d), a3(std::move(s)) { }
};

Well, here's the thing, there is actually no need at all for init in your example. 嗯,这就是事实,在你的例子中实际上根本不需要init The language rules already dictate default member initializer go first, then what ever you do in the member initializer list. 语言规则已经指定默认成员初始化程序首先,然后在成员初始化程序列表中执行的操作。 The members are initialized in declaration order . 成员按声明顺序初始化。 So you could really just define each c'tor as 所以你真的可以把每个c'tor定义为

A() upDBHandle(open_database(a1, a2, a3)) { }
explicit A(int i):a1(i), upDBHandle(open_database(a1, a2, a3)) {}
explicit A(double d):a2(d), upDBHandle(open_database(a1, a2, a3)) {}
explicit A(std::string s):a3(std::move(s)), upDBHandle(open_database(a1, a2, a3)) {} 
A(int i, double d, std::string s) : a1(i), a2(d), a3(std::move(s)), upDBHandle(open_database(a1, a2, a3)) {}

And that's it. 就是这样。 Some may say it's not good, since changing the class declaration can make it break. 有些人可能会说这不好,因为更改类声明可能会让它破裂。 But compilers are pretty good at diagnosing this. 但编译器非常擅长诊断这一点。 And I belong to the school of thought which says a programmer should know what they are doing, and not just build code by happy coincidence. 而且我属于思想学派,它说程序员应该知道他们在做什么,而不仅仅是通过快乐的巧合来构建代码。

You can even do that pre-C++11. 你甚至可以在C ++ 11之前做到这一点。 Sure, you don't have default member initializers, and would have to repeat the default values for each member (possibly name them somehow first, to avoid magic numbers), but that isn't related to the issue of initializing the member that depends on their initial value. 当然,您没有默认成员初始值设定项,并且必须重复每个成员的默认值(可能首先以某种方式命名它们以避免幻数),但这与初始化依赖的成员的问题无关在他们的初始价值。

Advantages over init ? 优于init优势?

  • Support for const members, however rare they may come. 支持const成员,但他们可能很少见。
  • Support for objects with no default state. 支持没有默认状态的对象。 Those cannot be default initialized and wait until init is called. 那些不能默认初始化并等到调用init
  • No need to add superfluous functions to your class, that are unrelated to its function. 无需为您的类添加多余的函数,这些函数与其函数无关。

Now, if the initialization code for the member is not trivial, you can still put it in a function. 现在,如果成员的初始化代码不是很简单,您仍然可以将它放在一个函数中。 A free function, that is hopefully static to your classes' translation unit. 一个免费的功能,希望你的班级翻译单元是静态的。 Like so: 像这样:

static std::unique_ptr<DatabaseHandle> init_handle(int a1, double a2, std::string const& a3) {
  // do other stuff that warrant a function block
  return open_database(a1, a2, a3);
}

A::A() upDBHandle(init_handle(a1, a2, a3)) { init(); }
A::A(int i):a1(i), upDBHandle(init_handle(a1, a2, a3)) {}
A::A(double d):a2(d), upDBHandle(init_handle(a1, a2, a3)) {}
A::A(std::string s):a3(std::move(s)), upDBHandle(init_handle(a1, a2, a3)) {} 
A::A(int i, double d, std::string s) : a1(i), a2(d), a3(std::move(s)), upDBHandle(init_handle(a1, a2, a3)) {}

That also means that you can have several of those for different members. 这也意味着您可以为不同的成员提供其中的几个。 So now the concern of initializing the members is more spread out. 因此,现在初始化成员的担忧更加分散。

Removing the need for the many c'tors can actually be done with something that Fred Larson suggested in his comment to your post. 消除对许多人的需求实际上可以通过Fred Larson在他对你的帖子的评论中建议的东西来完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM