我班级的可变参数模板构造函数不能修改我的类成员,为什么会这样呢?

问题描述 投票:20回答:2

我一直在研究我们教授的一项任务,在那里我必须解决一个可变参数模板类。问题是,我无法修改递归构造函数中的类成员。我无法弄清楚为什么会出现这种情况,只要它进入下一个构造函数调用,它就会丢弃我对变量的更改。

我尝试过的:

  • 使用指针int* count而不是int count
  • 使用二传手设置counter

我已经搜索了几个小时,但找不到解决方案。

头文件“test.h”:

#include <cstdarg>
#include <iostream>

class Counter {
private:
    int count = 0;
    int tmp;

public:
    template <typename... Rest> Counter (int t, Rest... rest) {
        count++;
        std::cout << "start recursive number " << count << "...\n";
        Counter(rest ...);
        tmp = t;
        std::cout << "end recursive number " << count << "...\n";
    }
    Counter (int t) {
        count++;
        tmp = t;
        std::cout << "reached end of recursive ->  " << count << "\n";
    }
};

main.cpp中:

#include "test.h"
int main () {
    Counter a {0, 1, 2, 3, 4};
}

我得到的输出:

start recursive number 1...
start recursive number 1...
start recursive number 1...
start recursive number 1...
reached end of recursive ->  1
end recursive number 1...
end recursive number 1...
end recursive number 1...
end recursive number 1...
c++ c++11 recursion variadic-templates
2个回答
24
投票

Counter(rest ...);创建一个未命名的临时对象,它不会递归调用此对象的构造函数。每个对象都使用自己的count生成,因此您获得了1 1 1 1的流

如果要将对象初始化委托给不同的构造函数,那么它应该存在于成员初始化列表中。这似乎不是一个好主意:

template <typename... Rest> Counter (int t, Rest... rest)
:   Counter{rest...}
{
    count++;
    std::cout << "start recursive number " << count << "...\n";
    tmp = t;
    std::cout << "end recursive number " << count << "...\n";
}

12
投票

正如VTT所解释的那样,在构造函数体内调用Counter()会创建一个新的Counter()对象。

您可以递归调用构造函数,但必须在初始化列表中执行此操作:查找“委托构造函数”以获取更多信息。

我还建议你不要在构造函数体内初始化(和修改)成员对象。

如果您的目标是使用参数的数量初始化count,并使用最后一个参数的值初始化tmp,我建议以下(基于“标签调度”)解决方案

class Counter
 {
   private:
      struct tag
       { };

      int count = 0;
      int tmp;

      Counter (tag tg, std::size_t c0, int t) : count(c0), tmp{t}
       { std::cout << "end: " << tmp << ", " <<count << "\n"; }

      template <typename... Rest>
      Counter (tag t0, std::size_t c0, int t, Rest... rest) 
         : Counter{t0, c0, rest...} 
       { std::cout << "recursion: " << tmp << ", " << count << "\n"; }

   public:
      template <typename... Rest>
      Counter (Rest... rest) : Counter{tag{}, sizeof...(Rest), rest...} 
       { std::cout << "start: " << tmp << ", " << count << "\n"; }
 };

您还可以避免使用标记调度和构造函数递归将rest...的递归委托给用于初始化static的方法(可能是constexpr以及tmp,如果需要)

class Counter
 {
   private:
      int count = 0;
      int tmp;

      static int getLastInt (int i)
       { return i; }

      template <typename ... Rest>
      static int getLastInt (int, Rest ... rs)
       { return getLastInt(rs...); }

   public:
      template <typename... Rest>
      Counter (Rest... rest)
         : count(sizeof...(Rest)), tmp{getLastInt(rest...)} 
       { std::cout << tmp << ", " << count << "\n"; }
 };

关闭主题:确切地说,您的Counter类不是“可变参数模板类”。

它是一个普通的(非模板)类,有一个(在我的第一个解决方案中是两个)可变参数模板构造函数。

- 编辑 -

OP问道

如果我需要将计数作为一个静态const变量和一个int数组在计算时间内作为类成员获得计数器的长度怎么办? (数组将填充所有构造函数参数)这是否在C ++的可能性范围内?

只有当计数器是类的所有实例之间的公共值时,静态const(也可能是constexpr)才有意义。

目前没有意义,因为你的Counter接受不同长度的初始化列表。

但是假设构造函数的参数的数量是模板参数(比如N)......在这种情况下,count只是N,可以是static constexpr。您可以为值定义std::array<int, N>(也是int[N],但我建议尽可能避免使用C风格的数组,并使用std::array),并使构造函数constexpr,您可以强制编译时初始化。

以下是一个完整的C ++ 14编译示例(使用std::make_index_sequencestd::index_sequence,遗憾的是,它只能从C ++ 14开始提供)。

观察我已经将f8中的main()变量定义为constexpr:只有这样你就可以强加(假装没有原则规则)f8被初始化编译时

#include <array>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t>
using getType = T;

template <std::size_t N, typename = std::make_index_sequence<N>>
struct foo;

template <std::size_t N, std::size_t ... Is>
struct foo<N, std::index_sequence<Is...>>
 {
   static_assert( sizeof...(Is), "!" );

   static constexpr auto count = N;

   const std::array<int, N> arr;

   constexpr foo (getType<int, Is> ... is) : arr {{ is ... }}
    { }
 };

int main ()
 {
   constexpr foo<8u>  f8 { 2, 3, 5, 7, 11, 13, 17, 19 };

   for ( auto const & i : f8.arr )
      std::cout << i << ' ';

   std::cout << std::endl;
 }

如果您可以使用支持C ++ 17的编译器,您还可以使用foo的演绎指南

template <typename ... Args>
foo(Args...) -> foo<sizeof...(Args)>;

所以没有必要解释定义f8的模板参数

// .......VVV  no more "<8u>"
constexpr foo  f3{ 2, 3, 5, 7, 11, 13, 17, 19 };

因为它是从构造函数的参数数量推导出来的。

© www.soinside.com 2019 - 2024. All rights reserved.