【问题标题】:How is std::string(char* char_array) implemented?std::string(char* char_array) 是如何实现的?
【发布时间】:2015-02-01 14:43:38
【问题描述】:

我很想知道字符串类如何实现从字符数组复制以初始化其内容。

我的猜测是这样的:

1:查找字符数组的长度,N。(这是怎么做的?一种粗略的方法是单独查看每个字符,直到找到空字符?使用更好的方法吗?)

2:分配N字节的存储空间。

3:使用strcpy逐字节复制每个元素。

显然这不是一个非常复杂的问题,我只是想知道以下是否(基本上或近似)等效:

std::string program_name(argv[0]);

std::string program_name;
int length = 0;
while(*(argv[0] + length) != '/0')
    ++ length;
++ length; // Depends on whether string contains the null character - usually I don't think it does?
program_name.resize(length); // Maybe use reserve instead?
std::cpy(program_name.data(), argv[0], length - 1); // Don't copy the null character at the end

反正就是这样。我没有尝试编译上面的伪代码,因为我对方法的概念感兴趣,而不是对如何完成此操作的细节感兴趣。

【问题讨论】:

  • std::string 的实现在 C++ 编译器附带的头文件中可用。使用来源,卢克。剧透警告:这几乎是你想象的方式,真的没有什么聪明的事情发生。
  • 您的步骤在语义上是等效的,但不会像优化的标准库实现那样高效。您的手动循环将比strlen 慢得多,并且调整大小不必要地初始化缓冲区只是为了立即再次覆盖它。
  • 扩展伊戈尔的评论,不可能有任何聪明的事情发生。 c 风格的字符串定义为以 NUL 字符结尾的连续字符数组。它不保留专用的长度字段。要回答您关于 std::basic_string 是否存储零终止符的问题:由于需要在 O(1) 中返回 c_str(),因此存储零终止符几乎是强制性的。
  • 您的解决方案在概念上类似,但错误:“修改通过数据访问的字符数组是未定义的行为。”

标签: c++ arrays string char


【解决方案1】:

简而言之,您的实现几乎就是它的工作原理。

忽略std::string 是从std::basic_string 实现的事实,它被模板化以处理存储在字符串中的各种数据类型(特别是“宽字符”),来自char * 的std::string 构造函数可以写成这样:

std::string(const char* init_value)
{
    size_t m_len = strlen(init_value);
    char *m_storage = new char[m_len+1];
    std::copy(m_storage, init_value, m_len+1);
}

当然,由于实际实现的继承和模板化性质,实际实现将更加间接[例如,可能具有“增长/分配”的特定功能]。

这是libcxx 的真正实现:

template <class _CharT, class _Traits, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
basic_string<_CharT, _Traits, _Allocator>::basic_string(const value_type* __s)
{
    _LIBCPP_ASSERT(__s != nullptr, "basic_string(const char*) detected nullptr");
    __init(__s, traits_type::length(__s));
#if _LIBCPP_DEBUG_LEVEL >= 2
    __get_db()->__insert_c(this);
#endif
}

__init 在哪里这样做:

template <class _CharT, class _Traits, class _Allocator>
void
basic_string<_CharT, _Traits, _Allocator>::__init(const value_type* __s, size_type __sz)
{
    if (__sz > max_size())
        this->__throw_length_error();
    pointer __p;
    if (__sz < __min_cap)
    {
        __set_short_size(__sz);
        __p = __get_short_pointer();
    }
    else
    {
        size_type __cap = __recommend(__sz);
        __p = __alloc_traits::allocate(__alloc(), __cap+1);
        __set_long_pointer(__p);
        __set_long_cap(__cap+1);
        __set_long_size(__sz);
    }
    traits_type::copy(_VSTD::__to_raw_pointer(__p), __s, __sz);
    traits_type::assign(__p[__sz], value_type());
}

它使用一些技巧将值存储在指针内[并使用相关的分配器进行分配,它可能不是new],并显式初始化结束标记[traits_type::assign(__p[__sz], value_type());,因为对__init的调用可能使用与 C 样式字符串不同的参数发生,因此不能保证结束标记。

traits_type::length()strlen

template <>
struct _LIBCPP_TYPE_VIS_ONLY char_traits<char>
{
...
    static inline size_t length(const char_type* __s) {return strlen(__s);}
....
};

当然,其他 STL 实现可能会使用不同的细节实现,但大致就像我的简化示例,但为了应对多种类型和重用代码而更加模糊。

【讨论】:

  • std::string 不是从std::basic_string 派生的;它是std::basic_string&lt;char&gt; 的别名。
  • @T.C.:重新措辞以反映。
  • “混淆”在某种程度上是必需的:例如,任何非公开都不能使用您可以合法使用的名称#define,并且该类需要使用分配器和特征。您提供的代码似乎也在使用短字符串优化。
  • @JamesKanze:当然,它被混淆是有正当理由的,但它仍然比你写的更复杂,如果你只是在家里和 1 个目标用户一起做一个爱好项目......; )
  • @MatsPetersson 甚至是我大部分时间都会做的专业工作:-)。
猜你喜欢
  • 2013-11-01
  • 2010-11-30
  • 1970-01-01
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-13
相关资源
最近更新 更多