避免std :: map :: find()的关键构造

XTF*_*XTF 21 c++ c++11

假设我有一个std::map<std::string, int>.std::string可以与没有std::string临时值的C字符串(const char*)进行比较.但是,map::find()似乎迫使我构造一个临时的std::string,这可能需要一个内存分配.我该如何避免这种情况?从概念上讲,它很容易,但STL似乎可以防止这种情况发生.

#include <map>

int main()
{
    std::map<std::string, int> m;
    m.find("Olaf");
}
Run Code Online (Sandbox Code Playgroud)

Quu*_*one 11

您的关注是真实的,并且C++ 11没有好的解决方法.

C++ 14通过添加模板化重载修复了这个问题std::map::find- 相关提案是N3657.在C++ 14中,您的程序将如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <algorithm>

class std_string {
    char *m_s;
public:
    std_string() { m_s = nullptr; }
    std_string(const char* s) { puts("Oops! A new std_string was constructed!"); m_s = strdup(s); }
    ~std_string() { free(m_s); }
    std_string(std_string&& ss) = delete;
    std_string(const std_string& ss) = delete;
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;

    bool operator< (const char* s) const { return strcmp(m_s, s) < 0; }
    bool operator< (const std_string& ss) const { return strcmp(m_s, ss.m_s) < 0; }
    friend bool operator< (const char* s, const std_string& ss) { return strcmp(s, ss.m_s) < 0; }
};

int main()
{
    {
        puts("The C++11 way makes a copy...");
        std::map<std_string, int> m;
        auto it = m.find("Olaf");
    }
    {
        puts("The C++14 way doesn't...");
        std::map<std_string, int, std::less<>> m;
        auto it = m.find("Olaf");
    }
}
Run Code Online (Sandbox Code Playgroud)

(std::less<>是广义的"小于"比较器,相当于operator<.C++ 03和C++ 11有一个破坏设计版本的比较器,它强制两个参数都是相同的类型.C++ 14最后做的对的.)

不幸的是,委员会似乎已经决定人们应该通过他们所有的C++ 11代码并更新每个容器以std::less<>用作比较器 - 它不仅仅是默认情况下发生的.这个决定没有充分的理由; 它只是它的方式.(请参阅上面关于设计破坏的评论.C++有一个坏习惯,在几年后引入"真实"版本之前引入破碎版本的东西.)

对于C++ 11,std::map::find只有一个重载(需要一个const Key&),因此任何解决方法都必然涉及将Key类型更改为更便宜 - 我们不能只是摆弄比较器,因为当执行到达比较器时,我们已经提升find了这种Key类型的论点.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <algorithm>

class std_string {
    char *m_s;
public:
    std_string() : m_s(nullptr) { }
    std_string(const char* s) : m_s(strdup(s)) { puts("Oops! A new std_string was constructed!"); }
    ~std_string() { free(m_s); }
    std_string(std_string&& ss) : m_s(nullptr) { std::swap(m_s, ss.m_s); }
    std_string(const std_string& ss) : m_s(strdup(ss.data())) { puts("Oops! A new std_string was constructed!"); }
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;

    const char* data() const { return m_s; }

    bool operator< (const char* s) const { return strcmp(m_s, s) < 0; }
    bool operator< (const std_string& ss) const { return strcmp(m_s, ss.m_s) < 0; }
    friend bool operator< (const char* s, const std_string& ss) { return strcmp(s, ss.m_s) < 0; }
};

struct string_or_ptr {
    union {
        const char* ptr;
        alignas(std_string) unsigned char str[sizeof (std_string)];
    } m_u;
    bool m_deep;

    char const* & ptr() { return m_u.ptr; }
    std_string& str() { return *reinterpret_cast<std_string*>(m_u.str); }
    char const* const & ptr() const { return m_u.ptr; }
    std_string const& str() const { return *reinterpret_cast<const std_string*>(m_u.str); }

    string_or_ptr() : m_deep(false) { ptr() = ""; }
    string_or_ptr(const char* s) : m_deep(false) { ptr() = s; }
    string_or_ptr(std_string&& s) : m_deep(true) { new ((void*)&str()) std_string(std::move(s)); }
    string_or_ptr(const std_string& s) : m_deep(true) { new ((void*)&str()) std_string(s); }
    ~string_or_ptr() { if (m_deep) str().~std_string(); }
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;


    operator const char*() const { return m_deep ? str().data() : ptr(); }

    bool operator< (const char* s) const { return strcmp((const char*)*this, s) < 0; }
    bool operator< (const std_string& ss) const { return (const char*)*this < ss; }
    bool operator< (const string_or_ptr& sp) const { return strcmp((const char*)*this, (const char*)sp) < 0; }
    friend bool operator< (const char* s, const string_or_ptr& sp) { return strcmp(s, (const char*)sp) < 0; }
    friend bool operator< (const std_string& ss, const string_or_ptr& sp) { return ss < (const char*)sp; }
};

int main()
{
    {
        puts("The C++11 way...");
        std::map<std_string, int> m;
        auto it = m.find("Olaf");
    }
    {
        puts("The C++11 way with a custom string-or-pointer Key type...");
        std::map<string_or_ptr, int> m;
        auto it = m.find("Olaf");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • *“这个决定没有充分的理由;这就是事实。”* 错误。考虑对于某些类型“stupid_string”,仅存在“char const*”的转换构造函数,而不存在比较运算符“operator&lt;(stupid_string const&amp;, char const*)”,仅存在“operator&lt;(stupid_string const&amp;, Stupid_string const&amp;)”。由于 `std::less&lt;&gt;` 将转发到比较运算符,**每次比较都会创建一个新的 `stupid_string`**。几年前,我在实际代码中看到过这个问题,因为 STLPort (IIRC) 默认情况下将其作为不合格的扩展执行此操作。 (2认同)
  • @dyp这似乎是一个不写`stupid_string`的好理由,但它似乎不是编译器为`std :: map <smart_string,int>`生成慢代码的一个很好的理由.("使愚蠢的代码变慢,智能代码快"在我看来比*严格更好*哲学比"使愚蠢的代码无法编译并使智能代码变慢.")换句话说,我不同意上面的位是否琐事是委员会对"std :: map"和`std :: set`的默认实现感到悲观的"*好*理由". (2认同)
  • 这个想法是旧代码保持尽可能快(并且不会变慢),而新代码可以使用新的函数对象类型。这也是一个正确性问题——在我遇到的问题中它导致了UB。 (2认同)