【问题标题】:Class data member type based another data member?基于另一个数据成员的类数据成员类型?
【发布时间】:2016-09-10 07:30:39
【问题描述】:

假设我有一个基类foo 和两个派生类AB。然后我有另一个类bar,它有一个数据成员xyz,可以是A,或者,但类型取决于其他数据成员x_type,@987654330 @ 和 z_type,并且这些值在编译时不可用。我虽然关于使用模板数据成员并在构造函数中定义类型,在那里我得到了类型的值,但显然这至少在 C++11 中是不可能的。那么如何进行呢?

class foo{
public:
  foo(double);
  int x_type;
  virtual double do_something(double, int) = 0;
};

class A: public foo {
public:
  A(double, double);
  double do_something(double, int);

private:
  double z1;
  double z2;
};

class B: public foo {
public:
  B(double);
  double do_something(double, int);

private:
  double w;
};

class bar {
public:
  bar();
  double do_something2(int);

private:
  int x_type;
  int y_type;
  int x_type;
  x; // these are either A or B...
  y;
  z;
};

在构造函数中我会有类似

if(x_type == 1){
  x = A(arg1, arg2);
} else {
  x = B(arg3);
}

在我的实际应用程序中,可能会有更多数量的派生类和未知类型的数据成员。我想知道是否可以使bar 成为具有多个模板参数的模板类,但我不确定这是否可能,因为参数类型取决于另一个参数?

【问题讨论】:

  • 通过整数值枚举类型是 Evil™ 反模式。这可能对应于带有指向多态类foo 的指针的设计。然后应该有一个虚拟析构函数。

标签: c++ class templates c++11


【解决方案1】:

您需要使用多态性并利用通用基类 Foo:

private:
  int x_type;
  int y_type;
  int x_type;
  std::unique_ptr<Foo> x; // these are either A or B...
  std::unique_ptr<Foo> y;
  std::unique_ptr<Foo> z;

};

然后在您的构造函数中,您可以从正确的类型创建 x y z:

if(x_type == 1){
  x.reset(new A(arg1, arg2));
} else {
  x.reset(new B(arg3));
}

最好将创建正确 Foo 实例的代码移到所谓的“工厂”类或函数中,以隐藏决策逻辑和构造细节(有时可能非常复杂)。

【讨论】:

    【解决方案2】:

    如果所有可用于xyz 的类型全部都派生自一个公共基类,即基指针解决方案,std::unique_ptr (+ 1 for Lyubomir Stankov),是(恕我直言)一个很好的解决方案。

    但是你问“是否可以让 bar 成为具有多个模板参数的模板类”。

    是的:这是可能的。不是很优雅(恕我直言),但可能。

    为了好玩,我提出了以下解决方案,但我认为,在更一般的情况下(请注意,在我的示例中,AB 是不相关的类,而不是从 foo 派生而来)有用(希望如此)

    #include <tuple>
    #include <string>
    #include <utility>
    
    class A
     {
       private:
          double       d;
          std::string  s;
    
       public:
          A (double d0, std::string s0) : d { d0 }, s { s0 } { }
     };
    
    class B
     {
       private:
          long l;
    
       public:
          B (long l0) : l { l0 } { }
     };
    
    template <typename Tx, typename Ty, typename Tz>
    class bar
     {
       private:
    
          template <typename ... Ts>
             using tpl = std::tuple<Ts...>;
    
          template <std::size_t ... Is>
             using is = std::index_sequence<Is...> const;
    
          template <std::size_t N>
             using mis = std::make_index_sequence<N>;
    
          Tx  x; 
          Ty  y;
          Tz  z;
    
          template <typename ... Tsx, std::size_t ... Isx,
                    typename ... Tsy, std::size_t ... Isy,
                    typename ... Tsz, std::size_t ... Isz>
          bar (tpl<Tsx...> const & tx0, is<Isx...> const &,
               tpl<Tsy...> const & ty0, is<Isy...> const &,
               tpl<Tsz...> const & tz0, is<Isz...> const &)
             : x { std::get<Isx>(tx0) ... },
               y { std::get<Isy>(ty0) ... },
               z { std::get<Isz>(tz0) ... }
           { }
    
       public:
    
          template <typename ... Tsx, typename ... Tsy, typename ... Tsz>
          bar (tpl<Tsx...> const & tx0,
               tpl<Tsy...> const & ty0,
               tpl<Tsz...> const & tz0)
             : bar(tx0, mis<sizeof...(Tsx)> {},
                   ty0, mis<sizeof...(Tsy)> {},
                   tz0, mis<sizeof...(Tsz)> {})
           { }
     };
    
    int main()
     { 
       bar<A, B, A>  aba{ std::make_tuple(2.3, "str1"),
                          std::make_tuple(4),
                          std::make_tuple(5.4, "str2") };
    
       bar<B, A, B>  bab{ std::make_tuple(3),
                          std::make_tuple(3.2, "str3"),
                          std::make_tuple(5) };
     }
    

    不幸的是,这个示例使用了 C++14 特性的 std::make_index_sequencestd::index_sequence

    如果你想在 C++11 中实现 foo,你可以实现下面的结构体 struct indexSeqstruct indexSeqHelper,来替换 std::index_sequencestd::make_index_sequence

    template <std::size_t ...>
    struct indexSeq
     { };
    
    template <std::size_t N, std::size_t ... Next>
    struct indexSeqHelper
     { using type = typename indexSeqHelper<N-1U, N-1U, Next ... >::type; };
    
    template <std::size_t ... Next >
    struct indexSeqHelper<0U, Next ... >
     { using type = indexSeq<Next ... >; };
    

    并定义ismis如下

      template <std::size_t ... Is>
         using is = indexSeq<Is...>;
    
      template <std::size_t N>
         using mis = typename indexSeqHelper<N>::type;
    

    【讨论】:

      【解决方案3】:

      所有变量的静态类型必须在编译时知道,所以它不能根据运行时对象的值而改变。完成这项工作的方法是使xyz都具有std::uniqe_ptr&lt;foo&gt;类型,然后在运行时动态分配AB对象:

      class bar {
      public:
          bar(some_type something) {
              if (something == some_value) {
                  b.x = new A(3.14, 12.34);
              } else {
                  b.x = new B(456.78);
              }
          }
      private:
          int x_type;
          std::unique_ptr<foo> x;
          //...
      };
      
      int main() {
          bar b(whatever());
      }
      

      在这种情况下,您还应该将foo::~foo() 声明为虚拟,以确保派生对象被正确销毁。

      完全消除x_type 和朋友并编写不关心x 的实际类型的代码,这也是一个普遍的好主意™。

      【讨论】:

        【解决方案4】:

        我想知道是否可以使 bar 成为具有多个模板参数的模板类,但我不确定这是否可能,因为参数类型取决于另一个参数?

        我不知道这是否有帮助,但我会放在这里以防万一。

        您会看到,模板的不同特化可以从不同的类继承。这样你就可以拥有:

        // fwd decl
        template <int kind> class bar;
        
        template <> class bar<1> : public A {
        public:
          bar(double x, double y) : A(x,y) { }
        };
        
        template <> class bar<2> : public B {
        public:
          bar(double a) : B(a) { }
        };
        

        在稍后阶段,当您使用class C : public foo 时,您只需将另一个kind 分配给新的bar 模板特化,就可以了:使用bar 作为统一的名称(警告...但不是统一类型 - 除了常见的 foo 祖先。bar&lt;1&gt;bar&lt;2&gt; 将是两种不同的类型)

        所以,好吧,如果你不想继承,你可以通过不同的has-a 在特定的bar 模板特化中拥有它。

        喜欢

        template <int kind> class bar;
        
        template <> class bar<1> {
           A val;
        public:
          bar(double x, double y) : val(x,y) { }
        
          void doSomething2(...) {
            // use the val of type A
          }
        };
        template <> class bar<2> {
           B val;
           double y_;
        public:
          bar(double x, double y) : val(x), y_(y) { }
        
          void doSomething2(...) {
            // use the val of type B and a separate y_
          }
        };
        

        【讨论】:

          【解决方案5】:

          我虽然关于使用模板数据成员和定义类型 构造函数,我在其中获取类型的值,但显然 至少在 C++11 中是不可能的

          C++11 提供了根据某些参数处理模板化构造的标准模式,方法是使用make_* 模板函数创建适当类型的对象。参见例如make_tuple函数:

          auto t = std::make_tuple(1, "abc", 1.0); 
          // decltype(t) == std::tuple<int, char const*, double>
          

          这可以通过创建模板类和模板构造函数来实现:

          template <class T>
          struct foo { 
             T t; 
             foo(T t): t(t) { }  
          };
          
          template <class T>
          foo<T> make_foo(T t) { return foo<T>(t); }
          

          【讨论】:

            猜你喜欢
            • 2022-01-10
            • 2019-10-11
            • 1970-01-01
            • 2010-11-14
            • 2014-05-23
            • 1970-01-01
            • 1970-01-01
            • 2017-02-22
            • 2010-10-16
            相关资源
            最近更新 更多