std::initializer_list를 사용한 초기화에는 복사가 포함됩니다.

12735 단어 cppoptimizationmemory
STL 컨테이너를 초기화하는 편리한 방법은 다음과 같이 초기화 목록을 사용하는 것입니다.

auto data = std::vector<std::string>{"example", "input", "data"};


주의할 점이 있습니다. 벡터 생성자의 인수는 먼저 생성된 다음 복사됩니다. std::initializer_list 의 특성입니다. 매개변수가 일부 리소스 등을 처리하기 때문에 사소하게 복사할 수 없는 경우 문제가 될 수 있습니다. 짧은 문자열은 작은 문자열 최적화로 처리해야 하므로 문제가 되지 않지만 긴 문자열은 표준 라이브러리 구현에 따라 다릅니다. 작거나 그렇지 않은 경우)에는 세 가지 메모리 할당이 필요합니다. 하나는 문자열 생성 중, 두 번째는 벡터 항목용, 세 번째는 복사용입니다.

auto data = std::vector<std::string>{
    "long string that will likely be allocated on the heap"
}; // three memory allocations


메모리 할당 수를 추적하는 테스트 프로그램을 작성해 보겠습니다.

void* operator new (std::size_t count) {
    std::cout << "new " << count << " bytes\n";
    return std::malloc(count);
}

int main() {
    std::cout << "Vector with small string:\n";
    auto data = std::vector<std::string>{"small string"};
    std::cout << "\nVector with long string:\n";
    data = std::vector<std::string>{
        "long string that will likely be allocated on the heap"
    };
}

g++ test.cpp --std=c++17 -O2 -o test 에서 최적화된 g++ 9.4.0으로 컴파일했습니다. 프로그램 실행의 출력은 다음과 같습니다.

./test
Vector with small string:
new 32 bytes

Vector with long string:
new 56 bytes
new 32 bytes
new 56 bytes


여기서 우리는 작은 문자열 std::vector에 대해 std::string의 크기인 32바이트를 할당했음을 볼 수 있습니다. 긴 문자열을 처리하는 벡터를 생성하려면 3개의 메모리 할당이 필요합니다. 첫 번째는 매개변수 구성에서 가져오고 두 번째는 벡터 내부의 std::string에 대한 메모리 할당이고 마지막은 매개변수의 복사본입니다.

복사본을 제거하려면 피해야 합니다std::initializer_list . 대신 make_vector 함수를 작성해 봅시다. 내 초안은 다음과 같습니다.

template <typename T, typename... U>
std::vector<std::decay_t<T>> make_vector(T&& arg, U&&... args) {
    auto result = std::vector<std::decay_t<T>>{};
    result.reserve(1 + sizeof...(args));
    result.emplace_back(std::forward<T>(arg));
    (result.emplace_back(std::forward<U>(args)), ...);
    return result;
}


이제 매개변수를 구성하는 emplace_back 메서드로 전달되기 때문에 매개변수가 복사되지 않습니다. 중요한 것은 reserve 방법을 사용하여 한 번에 모든 매개변수에 대한 메모리를 할당하는 것입니다. 그렇지 않으면 복사 할당을 저장하지만 벡터 항목에 대한 중복 할당을 추가합니다.

테스트 프로그램을 수정해 보겠습니다.

int main() {
    std::cout << "String size: " << sizeof(std::string) << "\n";
    std::cout << "\nVector with small string:\n";
    auto data = std::vector<std::string>{"short vector"};
    std::cout << "\nVector with a long string:\n";
    data = std::vector<std::string>{
        "a long string that will likely be allocated on the heap"};
    using namespace std::literals;
    std::cout << "\nmake_vector with a long string:\n";
    data = make_vector(
        "a long string that will likely be allocated on the heap"s);
    std::cout << "\nVector with two long strings:\n";
    data = std::vector<std::string>{
        "a long string that will likely be allocated on the heap",
        "another long string that will likely be allocated on the heap"};
    std::cout << "\nmake_vector, two long strings:\n";
    data = make_vector(
        "a long string that will likely be allocated on the heap"s,
        "another long string that will likely be allocated on the heap"s);
}


이제 출력이 더 좋아 보입니다. 설명을 위해 주석을 추가했습니다.

String size: 32

Vector with small string:
new 32 bytes # vector item (sizeof std::string)

Vector with a long string:
new 56 bytes # the argument
new 32 bytes # the vector item
new 56 bytes # argument copy

make_vector with a long string:
new 56 bytes # the string argument
new 32 bytes # the vector item

Vector with two long strings:
new 56 bytes # the first argument
new 62 bytes # the second argument
new 64 bytes # memory for two vector items
new 56 bytes # copy of the 1st argument
new 62 bytes # copy of the 2nd argument

make_vector, two long strings:
new 62 bytes # the second argument
new 56 bytes # the first argument
new 64 bytes # memory for two vector items

make_vector를 사용하면 더 이상 문자열이 복사되지 않고 사용도 편리합니다.

좋은 웹페이지 즐겨찾기