如何有效地将大 std::string 的一部分转换为浮点数?

How to convert part of a big std::string into a float efficiently?

void OBJLoader::load_vec(const char* line_prefix) {
size_t jump = 0;
size_t strpos = 0;
if (line_prefix == "v ") {
    while ((strpos = buffer.find(line_prefix, strpos)) != string::npos) {
        strpos++;
        vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump));
        strpos += jump;
        vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump));
        strpos += jump;
        vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump));
        strpos += jump;
    }
    return;
}
//(...)

这是我用来将 buffer 字符串中的文本转换为浮点数并将其放入向量中的代码。字符串本身的重要部分有这样的结构:

v -0.893339 0.784809 0.891470
v -0.893339 -0.784809 0.891470
v -0.692655 -0.634043 0.017402
v 0.692655 0.586786 -0.017402
v -0.710057 0.651445 0.000000
v 0.710057 -0.604188 0.000000
v 0.017402 -0.571364 -0.674429
v -0.017402 0.618621 0.674429
v 0.000000 -0.636023 0.691831

使用 vcoord_vec.emplace_back(stof(&buffer.at(strpos), &jump)) 将单个值加载到向量中大约需要 200 毫秒,同时:

void OBJLoader::load_vec(const char* line_prefix) {
    char* cbuffer = const_cast<char*>(buffer.c_str());
    if (line_prefix == "v ") {
        while (cbuffer = strstr(cbuffer, line_prefix)) {
            cbuffer++;
            vcoord_vec.emplace_back(strtof(cbuffer, &cbuffer));
            vcoord_vec.emplace_back(strtof(cbuffer, &cbuffer));
            vcoord_vec.emplace_back(strtof(cbuffer, &cbuffer));
        }
        return;
    }
//(...)

在 489 毫秒内加载了 4 718 598 个。将已知索引处的字符串的一部分以相当的效率转换为任何类型的数字会是什么"C++ way"?

对于像您正在使用的具有 space 分隔值的真正基本格式,您可以使用 C++ 流:

#include <sstream>

/*...*/

char id; 
float v[3];

std::istringstream iss("v 123.4 12.5 0262.2");
iss >> id;
if (id == 'v') {
    iss >> v[0] >> v[1] >> v[2];
}

您使用的 std::stof 有误。当它期望 const std::string&.

时,您传递给它 char*

其结果是在每次调用 std::stof 时从 char* 隐式转换为 std::string,从 [=17] 开始复制 buffer 的全部内容=] 到它的空终止符到由函数参数转换创建的临时。

如果可用,请首选 std::from_chars (C++17),否则我建议坚持使用 std::strtof 进行转换(这并不意味着您必须继续使用 strstr ). std::stof 指定无论如何都会调用 std::strtof

这是我在 3D 图形引擎中使用的实用程序 class。为了使它与我的示例一起使用,您需要有一个版本的 GLM 库可用,否则您可以注释掉所有与 GLM 相关的内容以对单个值进行简单的基本转换。但是,我在这里展示它是为了演示如何将字符串转换为不同的数字类型,包括用户定义的类型。

您不能构造此 Utility class object 的实例,因为它的构造函数已被删除并且没有成员变量。只是collection个相似或相关的功能,主要是string个操作方法。

如果您花时间通读 class,您会发现我正在使用静态函数将字符串转换为所需的类型。

class确实有2个私有函数模板,string_to_value定义在class之外的header和定义的get_value在 cpp 文件中,因为有 3 个基于 return 类型的重载或 template specialization 函数。它们分别是 intunsignedfloat。我还包括了所有 glm 矢量类型的重载 operator<<,以便于打印。

我在 get_value 函数中使用 std::stofstd::stoistd::stoul。如果你想添加转换为 double 的能力,将它添加到这个 class 应该不难。

我的 class 比这大得多,但为了简短起见,我至少省略了一半。


现在是代码...

应用程序和输出

main.cpp

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

int main() {
    using namespace util;

    try {
        std::string str1("3,4,5");
        std::string str2("-4,-2,7");
        std::string str3("2.4,7.8,9.2");      

        glm::uvec3 uvec3 = Utility::convert_to_uvec3(str1);
        glm::ivec3 ivec3 = Utility::convert_to_ivec3(str2);
        glm::vec3   vec3 = Utility::convert_to_vec3(str3);

        std::cout << uvec3 << '\n';
        std::cout << ivec3 << '\n';       
        std::cout <<  vec3 << '\n';

        // test an exception case:
        glm::vec4 v4 = Utility::convert_to_vec4(str2);

    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

输出

(3,4,5)
(-4,-2,7)
(2.4,7.8,9.2)
util::Utility::convert_to_vec4 Bad conversion of [-4,-2,7] to vec4

实用程序Class

Utility.h

#ifndef UTILITY_H
#define UTILITY_H

#include <string>
#include <algorithm>
#include <glm/glm.hpp>

namespace util {

    class Utility {
    public:
        static std::string to_upper(const std::string& str);
        static std::string to_lower(const std::string& str);
        static std::string trim(const std::string& str, const std::string elements_to_trim = " \t\n\r");

        static unsigned     convert_to_unsigned(const std::string& str);
        static int          convert_to_int(const std::string& str);
        static float        convert_to_float(const std::string& str);

        static glm::vec2    convert_to_vec2(const std::string& str);
        static glm::vec3    convert_to_vec3(const std::string& str);
        static glm::vec4    convert_to_vec4(const std::string& str);

        static glm::ivec2   convert_to_ivec2(const std::string& str);
        static glm::ivec3   convert_to_ivec3(const std::string& str);
        static glm::ivec4   convert_to_ivec4(const std::string& str);

        static glm::uvec2   convert_to_uvec2(const std::string& str);
        static glm::uvec3   convert_to_uvec3(const std::string& str);
        static glm::uvec4   convert_to_uvec4(const std::string& str);

    private:
        Utility() = delete;
        Utility(const Utility& c) = delete;
        Utility& operator=(const Utility& c) = delete;

        template<typename T>
        static bool string_to_value(const std::string& str, T* value, unsigned num_values);

        template<typename T>
        static T get_value(const std::string& str, std::size_t& remainder);
    };

    template<typename T>
    static bool Utility::string_to_value(const std::string& str, T* value, unsigned num_values) {
        int num_commas = std::count(str.begin(), str.end(), ',');
        if (num_commas != num_values - 1) return false;

        std::size_t remainder;
        value[0] = get_value<T>(str, remainder);

        if (num_values == 1) {
            if (str.size() != remainder) {
                return false;
            }
        } else {
            std::size_t offset = remainder;
            if (str.at(offset) != ',') {
                return false;
            }

            unsigned last_indx = num_values - 1;
            for (unsigned u = 1; u < num_values; ++u) {
                value[u] = get_value<T>(str.substr(++offset), remainder);
                offset += remainder;
                if ((u < last_indx && str.at(offset) != ',') ||
                    (u == last_indx && offset != str.size())) {
                    return false;
                }
            }
        }
        return true;
    }

} // namespace util

std::ostream& operator<<(std::ostream& out, const glm::ivec2& v2Value);
std::ostream& operator<<(std::ostream& out, const glm::ivec3& v3Value);
std::ostream& operator<<(std::ostream& out, const glm::ivec4& v4Value);

std::ostream& operator<<(std::ostream& out, const glm::vec2& v2Value);
std::ostream& operator<<(std::ostream& out, const glm::vec3& v3Value);
std::ostream& operator<<(std::ostream& out, const glm::vec4& v4Value);

std::ostream& operator<<(std::ostream& out, const glm::uvec2& v2Value);
std::ostream& operator<<(std::ostream& out, const glm::uvec3& v3Value);
std::ostream& operator<<(std::ostream& out, const glm::uvec4& v4Value);

#endif // UTILITY_H

Utility.cpp

#include "Utility.h"

#include <exception>
#include <sstream>

namespace util {
    std::string Utility::to_upper(const std::string& str) {
        std::string result = str;
        std::transform(str.begin(), str.end(), result.begin(), ::toupper);
        return result;
    }

    std::string Utility::to_lower(const std::string& str) {
        std::string result = str;
        std::transform(str.begin(), str.end(), result.begin(), ::tolower);
        return result;
    }

    std::string Utility::trim(const std::string& str, const std::string elements_to_trim) {
        std::basic_string<char>::size_type first_index = str.find_first_not_of(elements_to_trim);
        if (first_index == std::string::npos) {
            return std::string(); // nothing left
        }

        std::basic_string<char>::size_type last_index = str.find_last_not_of(elements_to_trim);
        return str.substr(first_index, last_index - first_index + 1);
    }

    template<>
    float Utility::get_value(const std::string& str, std::size_t& remainder) {
        return std::stof(str, &remainder);
    } // getValue<float>

    template<>
    int Utility::get_value(const std::string& str, std::size_t& remainder) {
        return std::stoi(str, &remainder);
    } // getValue<int>

    template<>
    unsigned Utility::get_value(const std::string& str, std::size_t& remainder) {
        return std::stoul(str, &remainder);
    } // getValue<unsigned>

    unsigned Utility::convert_to_unsigned(const std::string& str) {
        unsigned u = 0;
        if (!string_to_value(str, &u, 1)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to unsigned";
            throw std::exception(stream.str().c_str());
        }
        return u;
    }

    int Utility::convert_to_int(const std::string& str) {
        int i = 0;
        if (!string_to_value(str, &i, 1)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to int";
            throw std::exception(stream.str().c_str());
        }
        return i;
    }

    float Utility::convert_to_float(const std::string& str) {
        float f = 0;
        if (!string_to_value(str, &f, 1)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to float";
            throw std::exception(stream.str().c_str());
        }
        return f;
    }

    glm::vec2 Utility::convert_to_vec2(const std::string& str) {
        glm::vec2 v2;
        if (!string_to_value(str, &v2[0], 2)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to vec2";
            throw std::exception(stream.str().c_str());
        }
        return v2;
    }

    glm::vec3 Utility::convert_to_vec3(const std::string& str) {
        glm::vec3 v3;
        if (!string_to_value(str, &v3[0], 3)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to vec3";
            throw std::exception(stream.str().c_str());
        }
        return v3;
    }

    glm::vec4 Utility::convert_to_vec4(const std::string& str) {
        glm::vec4 v4;
        if (!string_to_value(str, &v4[0], 4)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to vec4";
            throw std::exception(stream.str().c_str());
        }
        return v4;
    }

    glm::ivec2 Utility::convert_to_ivec2(const std::string& str) {
        glm::ivec2 v2;
        if (!string_to_value(str, &v2[0], 2)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to ivec2";
            throw std::exception(stream.str().c_str());
        }
        return v2;
    }

    glm::ivec3 Utility::convert_to_ivec3(const std::string& str) {
        glm::ivec3 v3;
        if (!string_to_value(str, &v3[0], 3)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to ivec3";
            throw std::exception(stream.str().c_str());
        }
        return v3;
    }

    glm::ivec4 Utility::convert_to_ivec4(const std::string& str) {
        glm::ivec4 v4;
        if (!string_to_value(str, &v4[0], 4)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to ivec4";
            throw std::exception(stream.str().c_str());
        }
        return v4;
    }

    glm::uvec2 Utility::convert_to_uvec2(const std::string& str) {
        glm::uvec2 v2;
        if (!string_to_value(str, &v2[0], 2)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to uvec2";
            throw std::exception(stream.str().c_str());
        }
        return v2;
    }

    glm::uvec3 Utility::convert_to_uvec3(const std::string& str) {
        glm::uvec3 v3;
        if (!string_to_value(str, &v3[0], 3)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to uvec3";
            throw std::exception(stream.str().c_str());
        }
        return v3;
    }

    glm::uvec4 Utility::convert_to_uvec4(const std::string& str) {
        glm::uvec4 v4;
        if (!string_to_value(str, &v4[0], 4)) {
            std::ostringstream stream;
            // __FUNCTION__ works on windows otherwise user can exchange - modify to use __PRETTY_FUNCTION__
            stream << __FUNCTION__ << " Bad conversion of [" << str << "] to uvec4";
            throw std::exception(stream.str().c_str());
        }
        return v4;
    }

} // namespace util

std::ostream& operator<<(std::ostream& out, const glm::ivec2& v2Value) {
    out << "("
        << v2Value.x << ","
        << v2Value.y << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const glm::ivec3& v3Value) {
    out << "("
        << v3Value.x << ","
        << v3Value.y << ","
        << v3Value.z << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const glm::ivec4& v4Value) {
    out << "("
        << v4Value.x << ","
        << v4Value.y << ","
        << v4Value.z << ","
        << v4Value.w << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const glm::vec2& v2Value) {
    out << "("
        << v2Value.x << ","
        << v2Value.y << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const glm::vec3& v3Value) {
    out << "("
        << v3Value.x << ","
        << v3Value.y << ","
        << v3Value.z << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const glm::vec4& v4Value) {
    out << "("
        << v4Value.x << ","
        << v4Value.y << ","
        << v4Value.z << ","
        << v4Value.w << ")";
    return out;
}

std::ostream& operator<<(std::ostream& out, const glm::uvec2& v2Value) {
    out << "("
        << v2Value.x << ","
        << v2Value.y << ")";
    return out;
} 

std::ostream& operator<<(std::ostream& out, const glm::uvec3& v3Value) {
    out << "("
        << v3Value.x << ","
        << v3Value.y << ","
        << v3Value.z << ")";
    return out;
} 

std::ostream& operator<<(std::ostream& out, const glm::uvec4& v4Value) {
    out << "("
        << v4Value.x << ","
        << v4Value.y << ","
        << v4Value.z << ","
        << v4Value.w << ")";
    return out;
}

注:

在我的 string_to_value 函数中,我使用逗号作为定界符或字符串分隔符。您可以轻松地将其更改为 space 或其他一些字符,它应该为您提供相同的功能...