【问题标题】:Concatenate a family of string连接一系列字符串
【发布时间】:2017-08-25 07:37:52
【问题描述】:

我想编写一个函数,将std::string 的任何序列与幕后的 一个 malloc 连接起来。因此,首先需要计算字符串的总长度。该函数需要这样使用:

std::string s0 = ...;
std::string s1 = ...;
std::string s2 = ...;

std::string s = join(s0, s1, s2);

更好的join 将使用std::stringstd::string_view 的组合。如果我们可以添加字符串文字会更好。你会如何在 C++11 中编写这样的函数(它需要使用 gcc 4.8.5Visual Studio 2015 编译)?

【问题讨论】:

  • std::ostringstreamparameter packs 怎么样?当然这不是一个单一的分配,但如果你想要可变数量的参数,这是不可能的。如果您有固定数量的参数,那么它是可能的。您能否详细说明只有一次分配的要求?
  • 我不明白最后的投票:这不是要求异地资源 - 它是要求代码来解决问题。我认为这是一个糟糕的问题......
  • @Martin:为什么穷?
  • 性能是您在编程时应该担心的最后件事。首先要确保你有一些可读、可维护和工作的东西。然后,只有当“性能”不是“足够好”(大多数情况下确实足够好)时,你才能测量、分析和基准测试以找到热点和瓶颈,并专注于最糟糕的(记住记录你所做的所有优化,有很多 cmets)。然后重新开始测试、测量和优化,直到它足够好。
  • 实际上可能只进行一次分配,然后进行多个副本,但它需要两次传递参数:一次累积总大小,一次进行复制。它会比简单地添加到ostringstream 更快吗?我不知道。但我建议你开始阅读参数包,或在任何情况下都需要的可变模板参数。

标签: c++ string string-concatenation


【解决方案1】:

如果您有 string_view 类型,那么您可以只接受 string_views 的集合,将它们的大小相加,分配,然后复制。

std::string join(std::initializer_list<string_view> values)
{
    std::string result;
    result.reserve(std::accumulate(values.begin(), values.end(), 0, [](string_view s) { return s.length(); }));
    std::for_each(values.begin(), values.end(), [&result](string_view s) { result.append(s.data()); });
    return result;
}

【讨论】:

  • 这就是string_view::string_view(const std::string &amp;) 等的用途。 minor caveat:不要在join的参数中构造临时的std::strings
【解决方案2】:

对于range-v3,您可以使用concat

const std::string s = ranges::view::concat(s0, s1, " world\n");

【讨论】:

  • 很高兴知道。我会检查实现
【解决方案3】:

这是一个可能的实现:

template<typename... Args>
std::string join(const Args&... args)
{
    size_t size = 0;
    for( const std::string& s : { args... } )
        size += s.size();

    std::string result;
    result.reserve(size);
    for( const std::string& s : { args... } )
        result += s;
    return result;
}

演示:https://wandbox.org/permlink/qwG0LMewsHwVuGXN

【讨论】:

  • 请注意,要求所有args 都是同一类型(并且可以转换为std::string),您不能将std::string 和C 文字字符串混合使用。
【解决方案4】:

Benoit 几乎做到了,他的实现只需要必要的样板来处理不同的字符串类型。值得称赞的是,我必须承认这有点像样板。 :)

gcc -std=c++17编译

#include <string>
#include <string.h>
#include <string_view>
#include <iostream>

namespace detail {

    template<typename C> 
    size_t myStrLen(const std::basic_string<C>& s) { return s.length(); }

    template<typename C> 
    size_t myStrLen(const std::basic_string_view<C>& s) { return s.length(); }

    size_t myStrLen(char)    { return 1; }
    size_t myStrLen(wchar_t) { return 1; }

    size_t myStrLen(const char* s) { return strlen(s); }
    size_t myStrLen(const wchar_t* s) { return wcslen(s); }

    template<typename T, typename...Args>
    size_t myStrLen(T&& t, Args&&...args)
    {
        return myStrLen(std::forward<T>(t)) + myStrLen(args...);
    }

    template<typename C, typename T>
    void myConcat(std::basic_string<C>& result, T&& t)
    {
        result += t;
    }

    template<typename C, typename T, typename...Args>
    void myConcat(std::basic_string<C>& result, T&& t, Args&&...args)
    {
        result += t;
        myConcat(result, args...);
    }

}

template<typename C, typename... Args>
auto join(Args&&... args)
{
    std::basic_string<C> result;
    result.reserve(detail::myStrLen(args...));
    detail::myConcat(result, args...);
    return result;
}

template<typename... Args>
auto join(Args&&... args)
{
    return join<char>(args...);
}

template<typename... Args>
auto wjoin(Args&&... args)
{
    return join<wchar_t>(args...);
}

int main()
{
    std::string str{"hello"};
    std::string_view sv{"world"};

    std::wstring wstr{L"hello"};
    std::wstring_view wsv{L"world"};


    std::cout << join(str, " ", sv, '\n');
    std::wcout << wjoin(wstr, L" ", wsv, L'\n');

    return 0;
}

[编辑] 将模板 join&lt;charType&gt; 移出命名空间详细信息,因为这对其他模板构造很有用。

【讨论】:

  • 在 C++17 中,你甚至可以使用 template&lt;typename...Args&gt; std::size_t myStrLen(Args&amp;&amp;...args) { return (myStrLen(std::forward&lt;Args&gt;(args)) + ...); }
  • 可以,但是 MS 还不支持。 :(所以我还没有反应;)
【解决方案5】:

我相信使用完美转发和可变参数模板参数是可能的。

加入Str.hpp:

    class joinStr {
    public:
        template<typename... Args>
            joinStr(const std::string& first, Args&&... args): ret(""), size(0) {
                size = first.length();
                getSize((args)...);
                std::cout << "before reserve : " << ret.capacity() << std::endl;
                ret.reserve(size);
                std::cout << "after reserve : " << ret.capacity() << std::endl;
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        ~joinStr() {}
        std::string getStr() {
            std::cout << ret << std::endl;
            return ret;
        }
    private:
        std::string ret;
        int size;
        int getSize() {
            std::cout << "size : " << size << std::endl;
            return size;
        }
        template<typename... Args>
            int getSize(const std::string& first, const Args&... args) {
                size += first.length();
                return getSize((args)...);
            }
        template<typename... Args>
            int getSize(char* first, const Args&... args) {
                size += strlen(first);
                return getSize((args)...);
            }
        template<typename... Args>
            int getSize(const char* first, const Args&... args) {
                size += strlen(first);
                return getSize((args)...);
            }
        void join() {
            std::cout << "Final capacity : " << ret.capacity() << std::endl;
        }
        template<typename... Args>
            void join(const std::string& first, Args&&... args) {
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        template<typename... Args>
            void join(char* first, Args&&... args) {
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        template<typename... Args>
            void join(const char* first, Args&&... args) {
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
};

主要:

int main() {
    std::string s1 = "hello";
    std::string s2 = "world !";
    std::string s3 = "meatpopsicle";
    const char* s4 = "__yeah__";
    joinStr c(s1, s2, s3, s4);
    return 1;
}

请注意,我将它包装在一个类中,但您可以在一个类之外执行相同的操作。

作为奖励,它同时适用于 std::string、const char* 和 char*。

编辑:修复了在 getSize 中重复使用移动的值并在 Caleth's 建议之后为 const char* 添加了模板

编辑 2 : 允许在 const char* 之后传递 size_t 字段而不是使用 strlen 的版本。

 class joinStr {
    public:
        template<typename... Args>
            joinStr(const std::string& first, Args&&... args): ret(""), size(0) {
                size = first.length();
                getSize(args...);
                std::cout << "before reserve : " << ret.capacity() << std::endl;
                ret.reserve(size);
                std::cout << "after reserve : " << ret.capacity() << std::endl;
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        ~joinStr() {}
        std::string getStr() {
            std::cout << ret << std::endl;
            return ret;
        }
    private:
        std::string ret;
        int size;
        int getSize() {
            std::cout << "size : " << size << std::endl;
            return size;
        }
        template<typename... Args>
            int getSize(const std::string& first, const Args&... args) {
                size += first.length();
                return getSize((args)...);
            }
        // const char *
        template<typename... Args, typename T>
            int getSize(const char* first, const T& t, const Args&... args) {
                getSizeImpl<T>(first, t, std::integral_constant<bool, std::is_same<T, size_t>::value>());
                return getSize(t, args...);
            }
        template<typename T>
            void getSizeImpl(const char* first, const T& t, std::false_type) {
                // Case when the next argument is not a size_t type
                size += strlen(first);
            }
        template<typename T>
            void getSizeImpl(const char* first,const T& t, std::true_type) {
                // Do nothing
            }
        template<typename... Args>
            int getSize(size_t s, const Args&... args) {
                size += s;
                std::cout << "size_t " << s << " in getSize" << std::endl;
                return getSize(args...);
            }
        void join() {
            std::cout << "Final capacity : " << ret.capacity() << std::endl;
        }
        template<typename... Args>
            void join(const std::string& first, Args&&... args) {
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        template<typename... Args>
            void join(char* first, Args&&... args) {
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        template<typename... Args>
            void join(const char* first, Args&&... args) {
                ret.append(first);
                join(std::forward<Args>(args)...);
            }
        template<typename... Args>
            void join(size_t s, Args&&... args) {
                join(std::forward<Args>(args)...);
            }
};

主要:

int main() {
    std::string s1 = "hello";
    std::string s2 = "world !";
    std::string s3 = "meatpopsicle";
    const char* s4 = "__yeah__";
    joinStr c(s1, s2, s3, s4, (size_t)8);
    return 1;
}

【讨论】:

  • 如果任何参数是右值,您正在重用 joinStr::joinStr 中的移动值。你不应该转发到getSize。字符串文字也是const char *,因此它们将被强制进入const std::string&amp;重载
  • 是的,你是对的。我会根据你的建议更新代码。
  • @Clonk:看起来不错。如果字符串不是以空结尾的,有没有办法传递const char* s, int n
  • @Clonk: 清理一下,你可以做that
  • @Jarod42 这是处理第一个案例的一种更简洁且非常优雅的方式!出于好奇,您将如何实现在不使用递归函数的情况下在 char* 之后给出 size_t 参数的可能性?
猜你喜欢
  • 2017-05-14
  • 2016-01-03
  • 1970-01-01
  • 2021-05-12
  • 2020-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-24
相关资源
最近更新 更多