【问题标题】:How to populate a const member array based on a constructor argument?如何根据构造函数参数填充 const 成员数组?
【发布时间】:2016-08-02 16:40:42
【问题描述】:

假设我有这个课程:

template<int N>
class A {
    public:
    A(const char *s) ...
    private:
    const char buf[N];
};

模板在那里,我可以在没有动态内存分配(要求)的情况下配置数组大小。 buf 成员是 const,因为它旨在在对象初始化后在对象的整个生命周期内保持不变。

澄清一下,我也无权访问 STL。

我有哪些选择来定义这个构造函数,以便我可以将s 的内容复制到buf 中?一种选择是const_cast,但我正在寻找不需要这个的替代方案。

【问题讨论】:

  • 你能把char buf[N]改成std::array吗?
  • @Jarod42 不,很遗憾。我无权访问 STL。
  • @Ana 你能复制libstdc++ 的来源吗?即使您无权访问std::array,您也将立即重新实现它。可能比最初的实现更糟糕。
  • 初始化常量成员的唯一方法是通过构造函数初始化列表。初始化数组的唯一方法是复制到其中。不幸的是,你不能复制到初始化列表中的数组中,这意味着你不能做你想做的事。你想完成什么? A 的类是做什么用的?您要解决的实际问题是什么?为什么buf 必须是常量,你不能让A 的实例改为常量吗?
  • 您可以使用MyArray 代替std::array 吗?

标签: c++ arrays initializer


【解决方案1】:

@Richard Hodges 提供的解决方案要求使用 char 数组初始化类,而不是 char const*,后者更改构造函数的签名。如果这对您的情况不起作用,那么这是一种更改签名的解决方案:

template<int N>
class A 
{
       template<size_t...Is>
       A(const char * s, std::index_sequence<Is...>)
        : _assert(std::strlen(s) <= N, "size of buffer is bigger than N"), 
          buf{ (Is, *s != '\0'? *s++: *s)... }
        {}

    public:
        A(const char *s) : A(s, std::make_index_sequence<N>()) {}

    private:
       throwable  _assert;
       const char  buf[N];
};

其中throwable 定义为:

 struct throwable
 {
    throwable(bool b, char const * message){
      if (not b) 
          throw std::invalid_argument(message);
    }
 };

使用throwable 可确保buf 不会使用大于N 字节的缓冲区进行初始化。但是,如果您的情况确保了这一点,因此不需要此检查,则可以将其删除。没有它,代码也应该可以工作,但我建议您至少将其保持在 debug 模式。

请注意,添加_assert 作为成员会使类的大小至少增加一个字节。为了避免这种情况,我们可以使用empty base class optimiation 来代替:

template<int N>
class A : private throwable
{
       template<size_t...Is>
       A(const char * s, std::index_sequence<Is...>)
        : throwable(std::strlen(s) <= N, "size of buffer is bigger than N"), 
          buf{ (Is, *s != '\0'? *s++: *s)... }
        {}
    public:
        A(const char *s) : A(s, std::make_index_sequence<N>()) {}

    private:
       const char  buf[N];
};

这为每个实例节省了 1 个字节。

【讨论】:

  • 我喜欢 throwable,如果你给它一个 constexpr 构造函数(和 A 一样),那么你可以声明变量 constexpr - 这会给你一个带有堆栈跟踪的编译时异常,如果字符串长度错误。
  • @M.M 谁不会使用 c++14,除非他们为一个智障组织工作?
  • @RichardHodges 荒谬评论
  • @Quentin:知道了。这真是一个很好的收获。谢谢。查看修复。
  • 这是一个不错的技巧。而且我不会发疯,这也很好:p
【解决方案2】:

您使用 index_sequence 和模板扩展。

#include <utility>
#include <cstdlib>


template<int N>
class A {

  template<size_t...Is>
    A(const char (&s)[N], std::index_sequence<Is...>)
    : buf{ s[Is]... }
  {}


    public:
    A(const char (&s)[N]) 
      : A(s, std::make_index_sequence<N>())
    {
    }

    private:
    const char buf[N];
};


int main()
{
  A<3> a("ab");

};

而且因为 const char[] 是字面量类型,所以也允许类为 constexpr:

#include <utility>
#include <cstdlib>


template<int N>
class A {

  template<size_t...Is>
    constexpr A(const char (&s)[N], std::index_sequence<Is...>)
    : buf{ s[Is]... }
  {}


    public:
    constexpr A(const char (&s)[N]) 
      : A(s, std::make_index_sequence<N>())
    {
    }

    private:
    const char buf[N];
};


int main()
{
  constexpr A<3> a("ab");

};

但这会改变签名...

好吧,这个:

#include <utility>
#include <cstdlib>
#include <cstring>


template<int N>
class A {

  template<size_t...Is>
    constexpr A(const char *s, size_t len, std::index_sequence<Is...>)
    : buf{ (Is <= len ? s[Is] : char(0))... }
  {}


    public:
    constexpr A(const char *s) 
      : A(s, strlen(s), std::make_index_sequence<N>())
    {
    }

    private:
    const char buf[N];
};


int main()
{
  constexpr A<10> a("ab");

};

【讨论】:

  • 请注意,您将签名更改为完全采用N char。
  • @RichardHodges:我已经解决了:P(见我的解决方案)
  • @Nawaz 取决于我猜的 N 代表什么 - 长度或限制。谁知道,它不是由OP指定的。我在自己的库中有一个这样的类,名为 value::immutable::string_type。我实际上是通过一个免费的模板函数推导出 N 的。这允许编译时字符串操作。
  • 顺便说一句,如果你支持char const*的截断,你也可以支持char const[],因为你需要推导出大小。
猜你喜欢
  • 1970-01-01
  • 2021-05-05
  • 1970-01-01
  • 2015-05-26
  • 1970-01-01
  • 2013-04-11
  • 2016-07-14
  • 2016-12-22
  • 2011-09-13
相关资源
最近更新 更多