将多态基 class 的子 class 数组传入构造函数的最简单语法

simplest syntax for passing in an array of subclasses of a polymorphic base class into a constructor

这是工作代码,我想简化其调用。让我们从调用开始:

Config c2( {
           new ConfigPairInt(  "one",   1   ),
           new ConfigPairDouble( "two",   2.0 ),
           new ConfigPairString( "three", "3" )
} );

我想摆脱的第一件事是 new。 (如果我这样做 new 我认为这是内存泄漏?)但是当我让 Config 的构造函数采用 vector<ConfigPair&> 并删除这些 new 我得到页面对我来说没有意义的错误。

我想摆脱的第二件事是类型 class 名称。如果我改为切换到大括号初始化器,那么编译器 知道大括号初始化器列表是 ConfigPair* 因此它看到的所有内容都应该是 [=19 的初始化器=] 或者它的子class 之一。 但编译器实际上搜索子class 构造函数吗?显然不是。 (我确实有一个想法是让 ConfigPair 完成其所有子classes 的工作,并给它三个不同的构造函数。它不再是多态的。并且不是多态的,然后我也可以摆脱 new 因为向量可以是这个 ConfigPair class 而不是 ConfigPair* 因为多态性而需要它。但是多态性似乎非常适合这个问题,所以我舍不得放弃它。)

我想去掉的第三件事是 vector 初始化器周围的大括号。我想我可以用参数包来做到这一点,但我无法获得 subclass 参数的结果列表来初始化向量。

class ConfigPair {
public:
  ConfigPair( const char* pszName_in ) :
    pszName( newstring( pszName_in ) )
  {
  };

  virtual ~ConfigPair() {
    free( (char*) pszName );
  };

  const char* pszName;
};



class Config {
public:
 
  Config( std::vector<ConfigPair*> apcpair )
  {
      for( ConfigPair* pcpair: apcpair )
          // do something
  }
};



class ConfigPairInt : public ConfigPair {
public:
  ConfigPairInt( const char* pszName_in, int iValue_in ) :
    ConfigPair( pszName_in ),
    iValue( iValue_in )
  {
  };

  int         iValue;
};



class ConfigPairDouble : public ConfigPair {
public:
  ConfigPairDouble( const char* pszName_in, double dValue_in ) :
    ConfigPair( pszName_in ),
    dValue( dValue_in )
  {
  };
  double      dValue;
};

  

class ConfigPairString : public ConfigPair {
public:
  ConfigPairString( const char* pszName_in, const char* pszValue_in ) :
    ConfigPair( pszName_in ),
    pszValue( newstring( pszValue_in ) )
  {
  };

  virtual ~ConfigPairString() {
    free( (char*) pszValue );
  };

  const char* pszValue;
};

这是我在你的案例中所做的原始草稿(就所描述的而言),评论中的解释:

using ValueType = std::variant<std::string,int,double>; // To get rid of lengthy
                                                        // type names, and 
                                                        // have them
                                                        // change- and  maintainable 
                                                        // at a single point in your 
                                                        // source code
class ConfigPair {
public:
  ConfigPair( const std::string& name_in, ValueType value ) : 
                              // ^^^^^^^ Ditch "Hungarian notation" for heaven's sake!
    name( name_in ) {}

  virtual ~ConfigPair() {}; // Destructor doesn't need to do anything
                            // std::string manages memory already

  std::string name; // Is compatible with const char* for initialization
  ValueType  value;
};

using PConfigPair = std::shared_ptr<ConfigPair>;
// or in case that Config should take full ownership of ConfigPair's
// using PConfigPair = std::shared_ptr<ConfigPair>;

class Config {
public:
  Config( std::vector<PConfigPair> apcpair )
  {
      for( PConfigPair pcpair: apcpair )
          // do something
  }
// Maybe store the vector here as class member, if Config should take full
// ownership of those ConfigPair's
// std::vector<PConfigPair> config_pairs;
//
// In any case you don't need to care about manual memory management
};

Config c2( { std::make_shared<ConfigPair>("one",1), // std::make_unique alternatively
             std::make_shared<ConfigPair>("two",2.0),
             std::make_shared<ConfigPair>("three","3") } );

文档:

您可以很容易地摆脱对 newvector 的使用,方法是对 Config 构造函数使用 variadic template,例如:

class Config
{
public:
    template<typename... Ts>
    Config(const Ts&... args)
    {
        const ConfigPair* arr[] = {&args...};
        // use arr as needed...
        for( const ConfigPair *arg: arr ) {
            // do something with arg...
        }
    }
};

int main()
{
    Config c2(
        ConfigPairInt("one", 1),
        ConfigPairDouble("two", 2.0),
        ConfigPairString("three", "3")
    );
    // use c2 as needed...
    return 0;
}

Live Demo

如果您想使用多态性,您根本无法摆脱派生类型名称,因为编译器需要知道要创建哪些类型。单独使用 brace-initializer 并不会告诉编译器要创建哪种类型:

Config c2(
    {"one", 1},    // error: WHICH type?
    {"two", 2.0},  // error: WHICH type?
    {"three", "3"} // error: WHICH type?
);

如果你想要这种语法,那么就完全摆脱多态性,只需要 ConfigPair 本身通过 std::variantstd::any 或等价物处理任何值类型。

AS Remy 和 Panta 在其他答案中指出:如果您想使用多态性,您根本无法摆脱派生类型名称,因为编译器需要知道要创建哪些类型。 由于这是调用者最常输入的内容,因此优先考虑消除它。这样就排除了多态性。

相反,用于不同类型对的虚拟基地 class 现在变成了一把“瑞士军刀”,能够完成其子class 的工作。它有各种构造函数,并增加了枚举的开销以了解调用了哪个构造函数。

public:
  ConfigPair( const char* pszName_in, int iValue_in ) {
    cfgpair.type    = TypeInt64;
    cfgpair.pszName = newstring( pszName_in );
    cfgpair.u.i     = iValue_in;
  }

  ConfigPair( const char* pszName_in, double dValue_in ) {
    cfgpair.type    = TypeDouble;
    cfgpair.pszName = newstring( pszName_in );
    cfgpair.u.d     = dValue_in;
  }

  ConfigPair( const char* pszName_in, const char* pszValue_in ) {
    cfgpair.type     = TypeSz;
    cfgpair.pszName  = newstring( pszName_in );
    cfgpair.pszValue = newstring( pszValue_in );
  }

鉴于 ConfigPair 现在是唯一被传递给 Config 构造函数的 class 意味着编译器对该构造函数的类型要求的认识允许它在调用时推断出对象类型。因此,class 名称被免除。

此外,由于数组内容现在是同质的,我们可以将 ConfigPair 本身放入数组中,而不是(最初需要)仅存储 指针 ConfigPair subclass 对象。因此,新的(或替代和 non-memory-leaking 但更冗长的 shared_pointer<ConfigPairXXX>() 符号)。

  Config( const char* pszName_in, // name only used for debugging messages
          std::vector<ConfigPair> acpair ) :
      Config( pszName_in )
  {
      for ( const ConfigPair cpair: acpair )
          cpair.Set( this );
  }

对于来电者:

Config c2( { { "one",   1   },
             { "two",   2.0 },
             { "three", "3" } } );

最后,为 braced-initializer-list 留下了外花括号。我探索了用参数包替换向量,但后来我无法弄清楚如何让编译器再了解该类型,因此再次需要该类型。就调用者的简单性而言,继续执行此操作将向前迈出十步,所以我毕竟坚持使用 braced-initializer-list。 (如果有另一种方法可以在不需要 class 名称的情况下摆脱这些外括号,请告诉我。)