【问题标题】:Best Practice for Fixed Size String Class固定大小字符串类的最佳实践
【发布时间】:2017-01-03 10:55:45
【问题描述】:

我想要一个固定大小的字符串类。理想情况下,该接口将匹配std::string 之一,唯一的区别是新类从不分配新内存。对于应该避免分配新内存的应用程序,它应该是一个方便的类。大小可以是静态的(在编译时已知)。

我认为有两种方法。第一个是围绕char 数组实现一个类,然后或多或少地实现std::string 具有的所有功能。我还必须实现一些运算符来创建具有给定固定大小字符串等的std::strings。

第二种方法,我什至不确定是否可行,是从std::string and 继承,覆盖所有可能改变字符串大小的函数。我在 Visual Studio 中查看了 basic_string 标头,它似乎不是虚拟的,所以我想这不是要走的路。

您认为实现此类的最佳方法是什么?

【问题讨论】:

  • 在 C++17 中使用 std::string_view,您可以非常简单地结合字符数组和字符串视图...
  • 为什么不直接包装 std::string 并且不允许让它增长的操作?
  • 为什么不应该分配内存?
  • @ruhigbrauner 并覆盖所有可能改变字符串大小的函数 -- 那么你剩下的只是std::array<char, some_number>,只有几个函数这可能可以使用 STL 算法来实现。
  • 实现一个分配器然后使用std::basic_string<CharT, std::char_traits<CharT>, MyFixedSizeAllocator<CharT>>怎么样?

标签: c++ size fixed stdstring string-length


【解决方案1】:

我继续创建了一个可以构建的简单类。结构如下:基类是一个仅声明接口,仅包含您想要拥有的构造函数类型的签名,以及必须在继承类中实现的所有函数的列表 - 类,因为它们是纯虚拟。派生类是具有实际实现的模板类。您不直接使用该类,因为有一个辅助函数模板,它采用您想要为您想要支持的每个构造函数类型传递的类型,并返回该类型。

#ifndef FIXED_STRING_H
#define FIXED_STRING_H

#include <string>

// This base class does not contain any member variables 
// and no implementations of any constructor or function
// it serves as a definition to your interface as well as
// defining what methods must be implemented.
class fixed_string_base {
protected:
    // The types of constructors you want to implement
    template<size_t fixed_size>
    explicit fixed_string_base( const char(&words)[fixed_size] ) {};

    // The types of things you want to leave to default
    fixed_string_base() = default;
    fixed_string_base( fixed_string_base const& ) = default;
    fixed_string_base( fixed_string_base&& ) = default;
    fixed_string_base& operator=( fixed_string_base const& ) = default; 
    fixed_string_base& operator=( fixed_string_base&& ) = default;
    virtual ~fixed_string_base() = default;
public:
    // Put all of your pure virtual methods here that fixed_string must implement;
    virtual char* c_str() = 0;
    virtual size_t size() const = 0;
    virtual size_t count() const = 0;
};

// This is the actual class that inherits from its non
// templated declaration interface that has the implementation of the needed constructor(s)
// and functions or methods that were declared purely virtual in the base class
template<size_t fixed_size>
class fixed_string_t  : public fixed_string_base {
private:
    size_t fixed_string_size_t = fixed_size;
    char fixed_string_[fixed_size];

public:
    //template<size_t fixed_size>
    explicit fixed_string_t( const char(&words)[fixed_size] ) {
        strncpy_s( fixed_string_, sizeof(char) * (fixed_size), &words[0], fixed_string_size_t );
        fixed_string_[fixed_size] = '\0';
    }

    // c_str returns the character array.
    virtual char*  c_str() { return fixed_string_; }
    // size gives the total size including the null terminator
    virtual size_t size() const { return fixed_string_size_t; } 
    // count gives the size of the actual string without the null terminator
    virtual size_t count() const { return fixed_string_size_t - 1; }

    // Defaulted Constructors and Operators
    fixed_string_t( fixed_string_t const& ) = default;
    fixed_string_t( fixed_string_t&& ) = default;
    fixed_string_t& operator=( fixed_string_t const& ) = default;
    fixed_string_t& operator=( fixed_string_t&& ) = default;
    virtual ~fixed_string_t() = default;

};

// Helper - Wrapper Function used to create the templated type
template<size_t fixed_size>
fixed_string_t<fixed_size> fixed_string(  const char(&words)[fixed_size] ) {
    return fixed_string_t<fixed_size>( words );
}

#endif // FIXED_STRING_H

使用它看起来像这样:

#include <iostream>
#include "FixedString.h"

int main() {
    auto c = fixed_string( "hello" );
    std::cout << c.c_str() << " has a size of " c.size() << " with\n"
              << "a character count of " << c.count() << std::endl;
    return 0;
}

目前唯一的情况是这个实体是不可修改的。字符串本身是固定的。这只是一个模式或演示您正在寻找的类型的设计模式可能是什么。您可以添加或扩展它,从中借用甚至完全忽略它。这是你的选择。

【讨论】:

    【解决方案2】:

    首先是围绕char 数组实现一个类,然后或多或少地实现std::string 具有的所有功能。

    这绝对是要走的路。它易于编写、易于使用且不易误用。

    template <size_t N>
    class fixed_string {
        char array[N+1];
        size_t size;
    
    public:
        fixed_string() : size(0) { array[0] = '\0'; }
    
        // all the special members can be defaulted
        fixed_string(fixed_string const&) = default;
        fixed_string(fixed_string&&) = default;
        fixed_string& operator=(fixed_string const&) = default;
        fixed_string& operator=(fixed_string&&) = default;
        ~fixed_string() = default;
    
        // ...
    };
    

    所有访问器(datac_strbeginendatoperator[])都是单行的。所有的搜索算法都很简单。

    唯一真正的设计问题是您希望突变在失败时做什么。那就是:

    fixed_string<5> foo("abcde");
    foo += 'f'; // assert? throw? range-check internally and ignore?
                // just not even add this and instead write a 
                // try_append() that returns optional<fixed_string&>?
    

    设计选择有利有弊,但无论你选择哪一个,每个功能的实现也会非常简洁。


    第二种方法,我什至不确定是否可行,是从std::string 继承并覆盖所有可能改变字符串大小的函数。我在 Visual Studio 中查看了 basic_string 标头,它似乎不是虚拟的,所以我想这不是要走的路。

    std::string 中的任何内容是否为virtual 与这是否是一个好主意的问题无关。你肯定想从:

    template <size_t N>
    class fixed_string : private std::string { ... }
    //                  ^^^^^^^^^
    

    因为你的类型肯定不适合与std::string 的 is-a 关系。这不是std::string,它只是根据它来实现。私有继承会使这段代码格式错误:

    std::string* p = new fixed_string<5>();
    

    因此您不必担心缺少virtual

    也就是说,从string 继承将导致实现比直接使用更复杂、效率更低,并且存在更多潜在陷阱。实现这样的事情可能可能,但我看不出这会是一个好主意。

    【讨论】:

      猜你喜欢
      • 2014-02-06
      • 2011-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 2010-11-10
      相关资源
      最近更新 更多