基于另一个数据成员的类数据成员类型

Class data member type based another data member?

本文关键字:数据成员 类型 另一个      更新时间:2023-10-16

假设我有一个基类foo和两个派生类AB。然后我有另一个类bar,它有一个数据成员x, y, z,它可以是A,或者,但是类型取决于其他数据成员x_type, y_typez_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具有多个模板参数的模板类,但我不确定是否有可能,因为参数类型取决于另一个参数?

您需要使用多态性并利用公共基类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实例的代码移到所谓的"工厂"类或函数中,以隐藏决策逻辑和构造细节(有时可能相当复杂),这是一个很好的实践。

如果所有可用于x, yz的类型都是所有派生自一个公共基类,则基指针解决方案,std::unique_ptr (Lyubomir Stankov +1),是(IMHO)一个很好的解决方案。

但是你问"是否有可能使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) };
 }

不幸的是,这个例子使用了std::make_index_sequencestd::index_sequence,它们是c++ 14的特性。

如果你想在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;

所有变量的静态类型必须在编译时已知,因此它不能根据运行时对象的值进行更改。实现此工作的方法是使x, yz都具有std::uniqe_ptr<foo>类型,然后在运行时动态分配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()为virtual,以便确保派生对象被正确地销毁。

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

我想知道是否有可能使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<1>bar<2>将是两个不同的类型)

那么,好吧,如果你不想要继承,你可以在特定的bar模板特化中通过不同的has-a来实现它。

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_
  }
};

我考虑使用模板数据成员并定义类型构造函数,在那里我得到类型的值,但显然至少在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); }