不同的可变参数arg子​​类构造函数

时间:2018-05-26 09:20:33

标签: c++ variadic-templates variadic

我有以下结构:

struct s0 {
    char name[64];
    s0* parent;
    int stackLevel;

    s0(s0* parent_, char* nameMask_, ...){
        va_list args;
        va_start(args, nameMask_);
        vsprintf_s(name, 64, nameMask_, args);
        va_end(args);
        parent=parent_;
    }

    ~s0(){}
};

struct s1 : s0 {
   int p1;

   s1(s0* parent_, int p1_, char* nameMask_, ...) : s0(parent_, nameMask_) {
        p1=p1_;
   }
}

我正在寻找让s1构造函数转发其可变参数的方法  到s0构造函数。我开始研究参数包和可变参数模板,但我无法看到它在这种情况下是如何工作的。我正在寻找的是可行的吗?

2 个答案:

答案 0 :(得分:0)

之前的一些说明......

  1. 变量参数添加了对C必不可少的东西。(没有它们,printf()族不可能在纯C中实现。)它基于宏来缩小唯一代码之间的差距以及与平台和硬件相关的各种实现。在C ++中,应尽可能地防止使用这些宏。 template提供了一种类型安全的替代方案。

  2. 考虑到printf()系列的C ++替换是具有各种重载移位运算符的流类,恕我直言,在某些情况下,具有格式字符串和不同数量的参数的原则很多更合适(例如,在运行时输出通知或日志消息,另外应该应用本地化,甚至可以改变格式化参数的输出顺序)。使用移位运算符的流类不能很好地用于此。几年前,当我们使用gtkmm时,我发现Glib::ustring::compose()非常适合这项工作。虽然,它(以及printf())不是"编译时间安全"它至少是类型安全的,并且可以轻松应用运行时检查。而且:AFAIK,它没有宏 - 只用纯C ++代码实现。

  3. 但是,考虑到上述免责声明,我试图理解/解决OP关于带有可变参数的实现的问题。在编写示例代码时,我才意识到为什么调用构造函数从另一个构造函数中接受va_list成为一个问题。遵循在SO: Passing variable arguments to another function that accepts a variable argument list的答案中很好地呈现以下代码形式的方案:

    struct s0 {
      s0(const char *fmt, va_list args);
    };
    
    struct s1: s0 {
      s1(const char *fmt, ...):
        va_list args;
        va_start(args, fmt);
        s0(fmt, args)
        va_end(args);
      { }
    };
    

    哎呀,错误!我真的很想知道是否有任何平台/硬件组合可以成功编译。因此,必须将基类构造函数的调用移动到正文中:

    struct s1: s0 {
      s1(const char *fmt, ...)
      {
        va_list args;
        va_start(args, fmt);
        s0(fmt, args);
        va_end(args);
      }
    };
    

    哎呀,错误了!除了我甚至不知道可能在函数体中调用构造函数之外,我无法阻止默认构造函数的隐式调用(在这种情况下是不可用的)。

    所以,在这种情况下,我最终确定va_list的构造函数不是解决方案。我没有看到任何其他方式来分离获取/处理va_list的函数:

    #include <iostream>
    #include <cstdio>
    #include <cstdarg>
    
    struct s0 {
        char name[64];
        s0 *parent;
        int stackLevel;
    
        s0(s0 *parent, const char *nameMask, ...):
          parent(parent)
        {
          va_list args;
          va_start(args, nameMask);
          vsnprintf(name, sizeof name, nameMask, args);
          va_end(args);
        }
    
        ~s0() = default;
    
      protected:
        // constructor for derived classes
        s0(s0 *parent): name(""), parent(parent) { }
    
        // construction of name
        void initName(const char *nameMask, va_list args)
        {
          vsnprintf(name, sizeof name, nameMask, args);
        }
    };
    
    struct s1: s0 {
        int p1;
    
        s1(s0 *parent, int p1, const char *nameMask, ...):
          s0(parent), p1(p1)
        {
          va_list args;
          va_start(args, nameMask);
          initName(nameMask, args);
          va_end(args);
        }
    
        ~s1() = default;
    };
    
    int main(int argc, char *argv[])
    {
      s0 s0(nullptr, "s0[%d]", 0);
      std::cout << "s0 '" << s0.name << "'\n";
      s1 s1(&s0, 1, "s1[%d]", 1);
      std::cout << "s1 '" << s1.name << "'\n";
      return 0;
    }
    

    输出:

    s0 's0[0]'
    s1 's1[1]'
    

    Live Demo on coliru

    注意:

    在实现示例时,我意识到vsprintf_s()是C11标准的一部分,但不是C ++ 11的一部分。因此,我将其替换为std::vsnprintf(),其效果非常相似。

答案 1 :(得分:0)

如您所知,如果您可以使用C ++ 11,则可以使用可变参数模板。 新的s0构造函数将类似于:

template<typename ... Args>
s0(s0* parent_, const char* nameMask_, Args... args) : parent(parent_) {
  sprintf(name, nameMask_, args...);
}

这种结构也可以用于s1,以便直接调用s0的构造函数。

但是,我认为有两个问题需要考虑:

  • 现在构造函数已经模板化,编译器会为每个参数列表创建一个新的构造函数。编译时间会增加。
  • 现在,构造函数中已知参数的类型。但是,代码不使用此数据,因为它调用使用可变参数的sprintf。使用不同的格式库可能会很有趣。

整个代码就像:

#include <iostream>
#include <cstdio>

struct s0 {
  char name[64];
  s0* parent; 
  int stackLevel;

  template<typename ... Args>
  s0(s0* parent_, const char* nameMask_, Args... args)
    : parent(parent_) {
    sprintf(name, nameMask_, args...);
  }
};

struct s1 : s0 {
  int p1;

  template<typename ... Args>
  s1(s0* parent_, int p1_, const char* nameMask_, Args... args)
    : s0(parent_, nameMask_, args...), p1(p1_) {
  }
};