【问题标题】:different variadic arg subclass constructors不同的可变参数 arg 子类构造函数
【发布时间】:2018-11-05 13:30:15
【问题描述】:

我有以下结构:

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 构造函数。我开始研究参数包和可变参数模板,但我只是看不出在这种情况下它是如何工作的。我正在寻找的东西是否可行?

【问题讨论】:

  • 经典的“C”解决方案是有一个函数(或者,在你的情况下,s0 中的构造函数),它接受一个va_list 参数,就像 vsprintf 一样。然后 s1 构造函数可以调用它。您将需要 s0 中的通用(私有)“帮助器”方法,两个 s0 构造函数都可以调用该方法来共同使用 va_list 并执行实际工作的初始化代码。或者也许 s0 根本不需要可变参数构造函数,这取决于您的用例。标签variadic-templates 在这里不合适,顺便说一句。你没有使用模板。
  • 您使用的是可变参数函数,而不是可变参数模板。如果您询问如何将可变参数传递给另一个函数,则回答为here
  • 我看到 s0 'helper' 构造函数如何处理 va_list 参数,但这对我没有帮助,因为在我的应用程序中被其他类调用的 s1 构造函数是“...”论点。在将其传递给 s0 构造函数之前,如何将其处理为 va_list 参数,因为它位于同一行?

标签: c++ variadic-templates variadic


【解决方案1】:

之前的一些笔记...

  1. 变量参数为 C 添加了一些必要的东西。(没有它们,printf() 系列几乎无法在纯 C 中实现。)它基于宏,缩小了唯一代码和不同实现之间的差距,非常依赖于平台和硬件。在 C++ 中,一般应尽可能避免使用此类宏。 templates 提供了一种类型安全的替代方案。

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

但是,考虑到上述免责声明,我试图理解/解决 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(),它的工作原理非常相似。

【讨论】:

    【解决方案2】:

    正如您所建议的,如果您可以使用 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_) {
      }
    };
    

    【讨论】:

      猜你喜欢
      • 2021-03-12
      • 1970-01-01
      • 1970-01-01
      • 2018-05-18
      • 2021-12-10
      • 2021-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多