std::insert

std::insert는 C++ 표준 라이브러리의 컨테이너 클래스에서 사용할 수 있는 멤버 함수로, 특정 위치에 하나 이상의 요소를 삽입하는 데 사용된다. std::insert의 장점은, 다양한 컨테이너 타입(e.g., std::vector, std::unordered_set 등등)에 쉽게 사용 가능하다는 것이다. 물론 각각의 컨테이너 타입에 따라 약간씩 다를 수 있다.

std::vector에서의 std::insert 사용법

std::vector에서 std::insert 함수는 주어진 위치에 요소를 삽입하는데 사용된다. 삽입 위치는 반복자로 지정하고, 삽입할 값도 함께 아래와 같이 제공되어야 한다. 또한, 특정 횟수만큼 값을 반복해서 삽입하는 것도 가능하다:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 4, 5};

    // 3을 vec[2] 위치에 삽입
    vec.insert(vec.begin() + 2, 3);

    // Output: 1 2 3 4 5
    std::cout << "Vector after insertion: ";
    for (int elem : vec) {
        std::cout << elem << " ";
    }
    std::cout << "\n";

    // 100을 vec[0] 위치에 3번 삽입 
    vec.insert(vec.begin(), 3, 100);

    // Output: 100 100 100 1 2 3 4 5
    std::cout << "Vector after multiple insertions: ";
    for (int elem : vec) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    return 0;
}

하지만 std::vector의 경우, 요소를 삽입할 때마다 컨테이너의 기존 요소들을 뒤로 밀어내야 하므로 큰 벡터에서는 성능상 비용이 클 수 있다. 그 대신, std::vector에서는 여러 std::vector를 하나로 합칠 때 std::insert를 활용하면 편리하다. 아래 예제는 네 개의 std::vector를 합치는 방법을 보여준다:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec1 = {1, 2, 3};
    std::vector<int> vec2 = {4, 5, 6};
    std::vector<int> vec3 = {7, 8, 9};
    std::vector<int> vec4 = {10, 11, 12};

    // vec1의 끝에 vec2의 요소들을 삽입
    vec1.insert(vec1.end(), vec2.begin(), vec2.end());

    // vec1의 끝에 vec3의 요소들을 삽입
    vec1.insert(vec1.end(), vec3.begin(), vec3.end());

    // Output: Combined vector: 1 2 3 4 5 6 7 8 9
    std::cout << "Combined vector: ";
    for (int elem : vec1) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    // std::make_move_iterator와 insert 함수의 콤-비네이숀으로 vec4의 요소들을 vec1의 끝에 '이동'시킬 수도 있음!
    vec1.insert(vec1.end(), std::make_move_iterator(vec4.begin()), std::make_move_iterator(vec4.end()));
    vec4.clear();

    // Output: Combined vector: 1 2 3 4 5 6 7 8 9 10 11 12
    std::cout << "After move iterator: ";
    for (int elem : vec1) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    return 0;
}

이를 통해 두 가지를 알 수 있는데, 첫 번째는 아래와 같이 for 문을 돌며 요소를 각각 타겟 vector vec1에 담아주던 행위를:

for (const auto& elem : vec4) {
    vec1.emplace_back(elem);
}

코드 한 줄로 간결히 표현할 수 있다는 것이다(코드의 간지 또한 덤으로 얻을 수 있다(?)). 그리고 두 번째로는 std::make_move_iterator를 사용하여 벡터의 요소를 복사 없이 ‘이동’시킬 수 있다는 것이다 (이동시킨다는 것이 중요하다!). 아무리 emplace_back이 push_back보다 효율적이라고 해도, std::make_move_iterator를 통해 요소를 이동시키는 것이 성능 측면에서 더욱 효율적임을 잊지 말자. 이는 다음 글 std::move에서 좀 더 자세히 다룰 예정이다.

std::set이나 std::map에서의 std::insert 사용법

마찬가지로 std::insert를 set이나 map 컨테이너에서도 사용이 가능하다.

#include <iostream>
#include <unordered_set>
#include <string>

int main() {
    // 두 개의 unordered_set 초기화
    std::unordered_set<std::string> set1 = {"apple", "banana", "mango"};
    std::unordered_set<std::string> set2 = {"banana", "orange", "grape"};

    // set2의 모든 요소를 set1에 병합
    set1.insert(set2.begin(), set2.end());

    // 병합된 unordered_set의 내용을 출력
    // Output: Combined set contents: grape apple orange banana mango
    std::cout << "Combined set contents: ";
    for (const std::string& fruit : set1) {
        std::cout << fruit << " ";
    }
    std::cout << std::endl;

    return 0;
}

주목할 점은, set이나 map container에서는 중복된 요소를 허용하지 않기 때문에, insert를 통해 요소를 병합할 때 중복된 요소는 무시된다는 것이다. 그래서 원래 같으면 아래와 같이 요소가 있는지 확인 후 삽입해야 하는데, insert를 사용하면 이 과정을 생략할 수 있다:

for (const auto& elem : set2) {
    if (set1.find(elem) == set1.end()) {
        set1.insert(elem);
    }
}

로보틱스의 경우에는 이를 통해 keyframes의 id를 다루거나, 혹은 multi-robot 상황에서 landmark들의 id를 unique하게 관리해야하는 상황에서 활용할 수 있다.

로보틱스에서 활용 사례

이전에도 말했듯이, 이런 함수형 프로그래밍은 병렬 처리를 할 때 꽃으로 사용된다. 아래는 kiss-icp에서 TBB를 활용할 때 insert를 통해 효율적으로 여러 correspondences를 하나로 취합하는 예제이다:

tbb::parallel_reduce가 무엇인지 자세히 알 필요는 없더라도 찬찬히 코드를 보면 이해할 수 있다. 1) 각 thread가 ‘1st lambda’ function을 통해 각 thread들이 첫 번째로 Correspondence type의 res를 리턴하고, 2) 이렇게 각 thread에서 리턴한 res1, res2, res3, …들을 (tbb::block_range를 통해 각각의 thread가 독립적으로 1st lambda function을 수행해서 임의의 값을 리턴함) 3) ‘2nd lambda’ function을 통해 임이의 두 res_ares_b를 효율적으로 병합하는 예제이다.

결론

Lambda expression과 STL에서 제공하는 algorithm을 사용하면 코드를 간결하게 작성할 수 있고, 병렬 처리를 할 때도 효율적으로 코드를 작성할 수 있다는 것을 확인할 수 있다.


Robotics 연구자/개발자를 위한 Modern C++ 시리즈입니다. 사용된 코드들은 여기에서 확인할 수 있습니다.

  1. Modern C++ for Robotics 1. Introduction
  2. Modern C++ for Robotics 2. 함수형 프로그래밍과 Lambda Expression
  3. Modern C++ for Robotics 3. Lambda Expression의 Anonymous Function과 Named Function
  4. Modern C++ for Robotics 4. std::for_each() 쉬운 설명
  5. Modern C++ for Robotics 5. std::insert() 쉬운 설명
  6. Modern C++ for Robotics 6. std::move() 쉬운 설명
  7. Modern C++ for Robotics 7. std::transform() 쉬운 설명
  8. Modern C++ for Robotics 8. std::accumulate() 쉬운 설명
  9. Modern C++ for Robotics 9. std::all_of(), std::any_of(), std::none_of() 쉬운 설명
  10. Modern C++ for Robotics 10. std::copy_if() 쉬운 설명
  11. Modern C++ for Robotics 11. std::find_if() 쉬운 설명
  12. Modern C++ for Robotics 12. std::remove_if() 쉬운 설명
  13. Modern C++ for Robotics 13. std::replace_if() 쉬운 설명