버블 정렬(Bubble Sort)

더보기
void optimizedBubbleSort(int a[], int n){
    for (int i = 0; i < n-1; ++i) {
        for (int j = 0; j < n-1-i; ++j)
            if (n[j] > n[j+1]) swap(n[j], n[j+1]);
    }
}

크거나 작은 수를 배열의 뒤쪽으로 밀면서 정렬한다. 시간 복잡도는 O(N^2)이다.

 

 

삽입 정렬(Insertion Sort)

더보기
void insertionSort(int a[], int n) {
    for (int i = 1; i < n; ++i) {
        int current = a[i];
        int prev = i - 1;
        
        while (0 <= prev && a[prev] > current) {
            a[prev + 1] = a[prev];
            --prev;
        }
        a[prev + 1] = current;
    }
}

배열을 정렬된 값들, 정렬되지 않은 값들의 영역으로 분리하여 앞으로 끌고오며 값을 당겨온다.

시간 복잡도는 O(N^2)이다. 또한 stable sort이다.

 

 

선택 정렬(Selection Sort)

더보기
void selectionSort(int a[], int n) {
    for (int i = 0; i < n-1; ++i) {
        for (int j = i + 1; j < n; ++j)
            if (a[i] > a[j]) swap(a[i], a[j]);
    }
}

버블 정렬과 더불어 가장 기본적인 정렬이고 버블 정렬과 다르게 정렬된 값을 앞에서부터 채워나간다.

시간 복잡도는 O(N^2)이다.

 

 

계수 정렬(Counting Sort)

더보기
void countingSort(int a[], int n) {
    int largest = -1; // 오름차순, 내림차순과 값의 범위에 따라 결정
    
    for (int i = 0; i < n; ++i)
        largest = max(largest, a[i]);
    
    vector<int> freq(largest+1, 0);
    
    for (int i = 0; i < n; ++i) {
        freq[a[i]]++;
    }
        
    int j = 0;
    for (int i = 0; i <= largest; ++i) {
        while (freq[i]-- > 0) {
            a[j] = i;
            ++j;
        }
    }
}

정렬될 값의 범위(Range)가 넓지 않고 범위를 알고 있을 때 유용하게 사용할 수 있다.

범위를 정확히 알지 못하더라도 최대값을 구해서 벡터를 사용하면 된다.

시간 복잡도는 O(Range + N)이다.

'이론 > 자료구조&알고리즘' 카테고리의 다른 글

Vector Data Structure  (0) 2023.02.08
Pointers & Dynamic Memory  (0) 2023.02.07
2D Arrays  (0) 2023.02.07
Character Arrays/Strings  (0) 2023.02.03
Arrays  (0) 2023.02.02

연속된 메모리 공간에 할당되고 모두 동일한 타입으로 사용된다.

 

void func(int arr[]); // sizeof(arr) : 8
// void func(int* arr);

int main() {
    int arr[] = { 1, 2, 3, 4, 5 }; // sizeof(arr) : 20
    func(arr);
    
    return 0;
}

배열을 함수의 인수로 넘겨주면 주소값이 전달되기 때문에 포인터의 크기가 나온다.

인수가 배열이라는 것을 명시적으로 알려줄 뿐 사실은 포인터와 완전히 동일하다.

 

◾ 배열의 요소를 뒤집을 때 투 포인터를 사용하면 시간 복잡도는 O(N/2), 공간 복잡도는 그대로이다.

 

부분 배열

◾ 배열의 크기가 N이라면 부분 배열들의 개수는 N^3에 근접한다.

◾ 3중 for문을 사용하면 배열의 부분 배열들을 출력할 수 있다. (i=0<n, j=0<n, k=i<=j)

 

부분 배열의 합 : 브루트 포스

더보기
int largestSubarraySum(int arr[], int n) {
    int largestSum = 0;

    for (int i = 0; i < n; ++i) {
        for (int j = i; j < n; ++j) {
            int subarraySum = 0;
            for (int k = i; k <= j; ++k) {
                subarraySum += arr[k];
            }
            largestSum = max(largestSum, subarraySum);
        }
    }
    return largestSum;
}

완전 탐색에 의해 계산되고 공간 복잡도는 O(1), 시간 복잡도는 O(N^3)이다.

반복문을 두개로 줄일 수 있으므로 O(N^2)까지 낮출 수 있다.

 

부분 배열의 합 : 누적합

더보기
int largestSubarraySum(int arr[], int n) {
    vector<int> prefix(n, 0);
    prefix[0] = arr[0];
    
    for (int = 1; i < n; ++i)
        prefix[i] = prefix[i-1] + arr[i];
        
    int largestSum = 0;

    for (int i = 0; i < n; ++i) {
        for (int j = i; j < n; ++j) {
            int subarraySub = i > 0 ? prefix[j] - prefix[i-1] : prefix[j];            
            largestSum = max(largestSum, subarraySum);
        }
    }
    return largestSum;
}

공간 복잡도는 O(N), 시간 복잡도는 O(N+N^2) = O(N^2)이다.

 

부분 배열의 합 : Kadane 알고리즘

더보기
int largestSubarraySum(int arr[], int n) {
    int currentSum = arr[0];
    int largestSum = arr[0];
    
    for (int i = 1; i < n; ++i) {
        currentSum = max(currentSum + arr[i], arr[i]);
        largestSum = max(largestSum, currentSum);
    }
    
    return largestSum;
}

현재 누적합과 최대 누적합을 별도로 관리하여 선형으로 계산해나간다.

공간 복잡도는 O(1), 시간 복잡도는 O(N)이다.

'이론 > 자료구조&알고리즘' 카테고리의 다른 글

Vector Data Structure  (0) 2023.02.08
Pointers & Dynamic Memory  (0) 2023.02.07
2D Arrays  (0) 2023.02.07
Character Arrays/Strings  (0) 2023.02.03
Basic Sorting Algorithms  (0) 2023.02.03
#include <iostream>

using namespace std;

struct TestStruct {
public:
	TestStruct(const int _num, const int _cnum, int* _pnum) : num(_num), cnum(_cnum), pnum(_pnum) {}
	~TestStruct() {
		if (nullptr != pnum) {
			delete pnum;
			pnum = nullptr;
		}
	}

private:
	int num;
	const int cnum;
	int* pnum;
};

int main() {
	FILE* pWriteFile = nullptr;
	if (errno_t err = fopen_s(&pWriteFile, "../Data/test.bin", "wb"))
		return 0;

	TestStruct* pTest1 = new TestStruct(10, 20, new int(30));

	fwrite(pTest1, sizeof(TestStruct), 1, pWriteFile);
	fclose(pWriteFile);

	delete pTest1;
	pTest1 = nullptr;

	/* ---------------- */

	FILE* pReadFile = nullptr;
	if (errno_t err = fopen_s(&pReadFile, "../Data/test.bin", "rb"))
		return 0;

	TestStruct* pTest2 = new TestStruct(50, 60, nullptr);

	fread(pTest2, sizeof(TestStruct), 1, pReadFile); // const 멤버 변수가 있는데 어떻게 가능한지
	fclose(pReadFile);

	delete pTest2;
	pTest2 = nullptr;

	return 0;
}

 

구조체 멤버변수 cnum은 const라서 초기화 이후 수정이 되면 안되는데 fread를 통해 스트림의 데이터를 구조체 크기만큼 덮어쓰니까 에러가 발생하지 않고 값이 바뀌어버림.

 

포인터나 레퍼런스가 아니라서 const_cast가 적용될리도 없고 애초에 c함수라서 const_cast가 존재하지 않음

대체 왜..?

'프로그래밍 > 공부' 카테고리의 다른 글

선언과 정의의 차이  (0) 2022.09.28
[자료구조] 힙  (0) 2022.07.23
[자료구조] 트리, 그래프  (0) 2022.07.23
Effective C++ [4] 설계 및 선언  (0) 2015.07.10
Effective C++ [3] 자원관리  (0) 2015.07.09

std::string 객체들을 담는 컨테이너에 삽입 함수를 이용해서 새 요소들을 추가한다고 하면 당연히 논리적으로 삽입 함수에 넘겨주는 인수의 타입은 std::string이어야 할 것이다.

그렇기는 한데 항상 참은 아닐 수도 있다.

 

std::vector<std::string> vs;
vs.push_back("xyzzy"); // 문자열 리터럴

문자열 리터럴은 std::string 객체가 아니라서 컨테이너의 타입과 다르다. 그런데 에러가 발생하지 않고 동작하는 이유가 무엇일까?

 

template<class T, class Allocator = allocator<T>>
class vector {
public:
    ...
    void push_back(const T& x);
    void push_back(T&& x);
    ...
};

push_back은 왼값과 오른값에 대해 오버로딩 되어있다.

문자열 리터럴이 인수로 전달되면 std::string != const char[] 이기 때문에 타입 불일치를 해소하기 위해 컴파일러는 문자열 리터럴로부터 임시 std::string 객체를 생성하고 push_back에 전달하는 코드를 산출한다.

 

vs.push_back("xyzzy");
vs.push_back(std::string("xyzzy"));

위의 코드를 마치 아래 코드처럼 취급하게 된다.

문제없이 작동하지만 성능을 고려한다면 그다지 좋지는 않다. 왜냐면 std::string의 생성자가 두번 호출되기 때문이다.

 

문자열 리터럴이 인수로 전달될 때, 임시 std::string 객체가 생성되면서 생성자가 호출된다.

임시 객체가 push_back의 오른값 매개변수 버전으로 전달되고 x의 참조에 묶이게 되는데, 이 때 이동 생성자에 의해 x의 복사본이 생성되며 생성자가 호출된다.

이후 push_back의 호출이 끝나는 즉시 임시 객체도 사라지며 소멸자가 호출된다.

총 2번의 생성과 한번의 소멸이 이루어진다.

 

만약 문자열 리터럴을 통해 임시 객체를 생성하지 않고 직접 전달할 수 있으면 임시 객체의 생성과 파괴 비용이 발생하지 않을 것이다.

 

 

이를 위해서는 push_back이 아닌 emplace_back을 호출하면 된다.

emplace_back은 전달된 인수를 이용해서 직접 객체를 생성하고 그 과정에 임시 객체가 관여하지 않게 된다.

완벽 전달을 이용하기 때문에 완벽 전달이 실패하는 경우들을 제외하고는 항상 효율적으로 동작한다.

 

push_back, push_front를 지원하는 표준 컨테이너는 emplace_back, emplace_front 역시 같이 지원한다.

그 외 삽입 함수들 역시 대응되는 생성 삽입 함수인 emplace를 제공한다.

 

생성 삽입 함수들은 삽입 함수들이 하는 모든 일을 할 수 있으며 이론적으로 적어도 삽입 함수들보다 덜 효율적으로 수행하는 경우는 없다.

 

 

항상 그런것은 아니지만 그렇지 않은 경우들을 특정짓는것은 쉽지 않고 대신 생성 삽입이 바람직할 가능성이 큰 상황들을 식별할 수 있는 발견법은 있다.

 

◾ 추가할 값이 컨테이너에 대입되는 것이 아니라 컨테이너 안에서 생성된다.

◾ 추가할 인수 타입들이 컨테이너가 담는 타입과 다르다.

◾ 컨테이너가 기존 값과의 중복 때문에 새 값을 거부할 우려가 별로 없다.

 

위의 세 가지 조건을 모두 만족하는 경우 거의 항상 생성 삽입이 삽입보다 성능이 좋다.

 

 

추가로 생성 삽입 함수를 사용할 때 고려하면 좋은 사항들도 알아보자.

 

자원 관리

자원 관리와 관련된 경우 emplace의 호출은 오히려 독이될수도 있다.

 

std::shared_ptr를 생성할 때, 커스텀 삭제자를 지정하는 경우에는 make_shared를 통해서 생성할 수 없다. 이 때는 std::shared_ptr의 생성자를 직접 호출하게 되는데, 이를 push_back을 통해 삽입하게 되면 임시객체로 인수를 전달하는것이 보통의 경우일 것이다.

임시 객체의 생성과 삭제 비용을 피하기 위해 emplace를 사용하는 것인데, 이런 경우에는 임시 객체의 생성 비용이 매우 가치가 있다.

 

만약 임시 std::shared_ptr 객체를 담을 복사본을 할당하는 도중 메모리 부족 예외가 발생한다면 예외가 push_back 밖으로 전파되며 임시 std::shared_ptr 객체가 파괴된다. 그와 함께 임시 std::shared_ptr 객체를 만들 때 동적 할당된 자원 역시 임시 std::shared_ptr 객체의 소멸자가 호출되며 같이 해제된다.

 

그러나 emplace_back을 호출하면 동적할당으로 만들어진 원시 포인터가 완벽 전달되고 할당이 실패하여 메모리 부족 예외가 발생하면 예외가 emplace_back 바깥으로 전파되며 원시 포인터가 사라지게 되어 메모리 누수가 발생한다.

 

 

자원 관리 객체들을 담은 컨테이너의 삽입 함수를 호출하는 경우, 일반적으로 삽입 함수의 매개변수 타입들은 자원의 획득과 그 자원을 관리하는 객체의 생성 사이에 아무것도 끼어들지 않음을 보장한다.

그에 반해 생성 삽입 함수는 완벽 전달에 의해 컨테이너의 메모리 안에서 생성할 수 있는 시점까지 지연된다.

그 사이에 예외가 발생하면 위와 같은 문제가 발생하는 것이다.

 

std::shared_ptr<Widget> spw(new Widget, killWidget);

ptrs.push_back(std::move(spw)); // 삽입
ptrs.emplace_back(std::move(spw)); // 생성 삽입

이번 예제에 한해서는 new 연산자를 통해 생성된 임시 객체를 직접 push_back이나 emplace_back에 전달하기보다 별도로 생성하여 오른값으로 전달해 주는것이 더 바람직하다.

 

explicit 생성자들과의 상호작용

C++11에 추가된 정규표현식(regex)은 nullptr을 받을 수 없다.

 

std::regex r = nullptr;
regexes.push_back(nullptr);

이것들은 컴파일이 안되는데,

 

std::vector<std::regex> regexes;

regexes.emplace_back(nulltpr);

이 코드는 컴파일이 된다.

 

std::regex는 암시적 변환에 의한 복사 연산이 일어나면 비용이 커질 우려가 있기 때문에 생성자가 explicit로 선언되어있다.

explicit 생성자는 복사 초기화를 사용할 수 없지만 직접 초기화는 사용할 수 있는 특징이 있다.

대입이나 push_back은 암시적 변환이 일어나며 복사 초기화를 시도하여 컴파일 에러가 발생하지만, emplace_back은 std::regex 객체로 변환될 어떤 객체가 아니라 std::regex의 생성자에 전달될 인수에 불과하기 때문에 컴파일이 가능한 것이다.

 

근데 컴파일만 가능할 뿐이지 미정의 동작이 일어날것은 뻔하다.

좀 더 일반화하자면 생성 삽입 함수는 직접 초기화를 사용하여 explicit 생성자를 지원하고 삽입 함수는 복사 생성자를 사용하여 explicit 생성자를 지원하지 않는다.

 

◾ 이론적으로, 생성 삽입 함수들은 종종 해당 삽입 버전보다 더 효율적이어야 하며, 덜 효율적인 경우는 절대로 없어야 한다.

◾ 실질적으로, 만일 (1)추가하는 값이 컨테이너로 대입되는 것이 아니라 생성되고, (2)인수 타입들이 컨테이너가 담는 타입과 다르고, (3)그 값이 중복된 값이어도 컨테이너가 거부하지 않는다면, 생성 삽입 함수가 삽입 함수보다 빠를 가능성이 아주 크다.

◾ 생성 삽입 함수는 삽입 함수라면 거부당했을 타입 변환들을 수행할 수 있다.

class Widget {
public:
    void addName(const std::string& newName) { names.push_back(newname); }
    void addName(std::string&& newName) { names.push_back(std::move(newName)); }
    ...
private:
    std::vector<std::string> names;
};

보편 참조를 사용하면 코드를 더 줄일 수 있을 것 같다.

 

class Widget {
public:
    template<typename T>
    void addName(T&& newName) { names.push_back(forward<T>(newName)); }
};

깔끔해졌지만 선언과 구현을 분리시킬 수 없기 때문에 구현부가 반드시 헤더파일에 있어야만 하는 문제가 있다.

게다가 왼값과 오른값에 대해 다르게 인스턴스화 되고 다른 타입들에 대해서도 다르게 인스턴스화 되어서 목적 파일이 커질 가능성이 있다. 뿐만 아니라 보편 참조로 전달하지 못하는 타입도 존재하고 부적절한 타입의 인수를 전달하면 난해한 에러 메시지를 출력할 수도 있다.

 

addName처럼 왼값은 복사하고 오른값은 이동하되 소스 코드와 목적 코드 양쪽에서 함수 하나만으로 다룰 수 있고 보편 참조의 여러 문제점들도 피할 수 있으면 완벽할 것만 같다.

이 문제점은 여지껏 준수해왔던 규칙중 하나를 포기하면 해결할 수 있다. 바로 사용자 정의 타입의 객체는 값으로 전달하지 말라는 것이다.

 

class Widget {
public:
    void addName(std::string newName) { names.push_back(std::move(newName)); }
    ...
};

std::move로 매개변수를 오른값으로 넘겨주지만 복사본을 이동시키는 것이기 때문에 호출자에게 영향이 미치지 않는다.

함수도 단 하나이기 때문에 코드 중복도 없고 위에서 언급한 문제점들도 발생하지 않는다.

 

그런데 값 전달로 인한 복사는 비용이 크다고 알고 있지 않았나?

그건 C++98까지의 이야기이고 C++11에서는 인수가 왼값일때만 복사되고 오른값일 때에는 이동 생성에 의해 생성된다.

 

Widget w;

std::string name("Bart");
w.addName(name); // 왼값 호출
w.addName(name + "Jenne"); // 오른값 호출

왼값 호출이 일어나는 경우 C++98때처럼 복사 생성이 일어난다. 하지만 오른값 호출이 일어나는 경우 이동 생성이 일어난다.

 

다만 주의사항이 몇가지 있다.

 

1. 왼값과 오른값에 대한 함수 오버로딩

호출자가 넘겨준 인수가 왼값이든 오른값이든, 해당 인수는 newName이라는 참조에 묶인다.

왼값의 경우 복사 1회, 오른값의 경우 이동 1회가 일어난다.

 

2. 보편 참조

함수 오버로딩과 마찬가지로 호출자의 인수는 newName 참조에 묶인다.

마찬가지로 왼값의 경우 복사 1회, 오른값의 경우 이동 1회가 일어난다.

 

3. 값 전달

호출자의 인수가 왼값이든 오른값이든 매개변수 newName은 반드시 생성된다.

왼값의 경우 복사 생성 1회, 오른값의 경우 이동 생성 1회가 일어난다.

다만 위의 구문같은 경우 move 연산이 한번 더 이뤄지므로 이동 1회가 추가된다.

 

 

그래서 무엇이 좋다는걸까?

항목의 제목을 다시 살펴볼 필요가 있다.

 

"이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달을 고려하라"

 

값 전달을 고려

값 전달을 "사용하라" 가 아니라 "고려하라" 이다.

장단점이 존재하기 때문에 값 전달이 정답이라고 말하기에는 무리가 있다.

 

복사 가능 매개변수

복사 가능 매개변수에 대해서만 값 전달을 고려해야 한다.

복사할 수 없는 매개변수는 반드시 이동 전용 타입일 것이고 해당 매개변수의 복사본이 생성된다면 반드시 이동 생성자를 통해서 생성된다.

또한 이동 전용 타입은 복사 생성자가 비활성화 되어있기 때문에 왼값 인수를 위한 함수 오버로딩을 정의해둘 필요가 없고 오른값 인수를 위한 함수만 제공하면 된다.

 

예를 들어 std::unique_ptr<std::string> 멤버 변수와 setter 멤버 함수가 있는 클래스를 생각해보자.

 

class Widget {
public:
    ...
    void setPtr(std::unique_ptr<std::string>&& ptr) { p = std::move(ptr); } // 보편 참조
private:
    std::unique_ptr<std::string> p;
};

 

이 때 오른값으로 setter를 호출하면 인수는 매개변수 ptr의 참조에 묶이게 되어 비용이 발생하지 않고 std::move에 의한 이동 연산 1회만 일어난다.

 

void setPtr(std::unique_ptr<std::string> ptr) { p = std::move(ptr); }

 

 

그런데 매개변수가 값으로 받게 구현되어 있다면 매개변수 ptr은 이동생성되고 std::move에 의해 한번 더 이동되어 총 2회의 이동 연산이 일어난다.

함수 오버로딩 방식에 비해 2배의 비용이 발생하게 된다.

 

이동이 저렴

이동이 저렴한 경우에는 이동 전용 타입을 값으로 전달하더라도 비용이 크게 문제되지 않는다.

 

항상 복사되는

class Widget {
public:
    void addName(std::string newName) {
        if ((newName.legnth() >= minLen) &&
            (newName.length() <= maxLen))
           {
               names.push_back(std::move(newName));
           }
    }
private:
    std::vector<std::string> names;
};

문자열의 길이가 너무 짧으면 컨테이너에 추가하지 않는 멤버 함수 addName의 구현이다.

이 구현의 문제는 조건이 충족되지 않아서 컨테이너에 아무것도 추가하지 않더라도 newName의 생성과 파괴는 함수 호출마다 이루어진다는 점이다. 만약 참조 전달 접근방식들이라면 발생하지 않았을 비용이다.

 

 

안타깝게도 이동이 저렴하고 복사 가능 타입에 대해 항상 복사를 수행하는 함수라고 하더라도 값 전달이 적합하지 않은 경우가 종종 발생한다.

그 이유는 생성에 의한 복사, 대입에 의한 복사 두 종류가 존재하기 때문이다.

 

생성에 의한 매개변수의 복사가 이뤄지는 경우 std::move호출에 의해 이동 연산이 한번씩 더 수행된다.

 

대입에 의한 매개변수의 복사가 이뤄지는 경우는 상황이 조금 더 복잡해진다.

 

class Password {
public:
    explicit Password(std::string pwd) : text(std::move(pwd)) {} // 생성에 의한 복사
    void changeTo(std::string newPwd) { text = std::move(newPwd); } // 대입에 의한 복사
private:
    std::string text;
};

 

std::string initPwd("very important and long long password");
Password p(initPwd);

이 경우 생성자에서 값 전달이 사용되기 때문에 이동 생성 1회의 비용이 발생한다. 오버로딩이나 완벽 전달을 사용했다면 발생하지 않는다.

 

std::string newPassword = "another password";
p.changeTo(newPassword);

도중에 값을 바꿀일이 발생하면 이제는 생성자가 아닌 대입 연산이 이뤄져야 한다.

이 때, 함수의 값 전달 접근방식 때문에 비용이 아주 커질 가능성이 생긴다.

 

changeTo에 왼값 인수가 전달되기 때문에 매개변수 newPwd가 생성될 때는 std::string의 복사 생성자가 호출된다.

std::string의 복사 생성자는 새로운 메모리를 할당하고 기존 메모리를 해제하게 된다.

생성과 해제 총 2번의 동적 메모리 관리가 일어나게 되는 것이다.

 

그런데 기존에 저장된 문자열이 새로운 문자열보다 더 길기때문에 메모리를 해제하지 않고 그대로 사용해도 무방하지만 별도로 처리하지 않으면 메모리 관리 2회가 발생하게 된다.

 

만약 기존에 저장된 문자열이 새로운 문자열보다 짧은 경우에는 반드시 메모리의 할당-해제가 일어나고 값 전달이나 참조 전달이나 비슷한 속도로 동작하게 된다.

한마디로 대입 기반 매개변수 복사의 비용은 대입에 관여하는 객체의 값에 의존하게 되는 것이다.

 

보통 이런 잠재적인 비용 증가는 왼값 인수가 전달되어 실제 복사 연산이 일어날 때만 필요하고 오른값 인수는 대부분 이동 연산으로 충분하다.

 

 

값 전달 방식이 효율적인 코드를 산출한다는 점을 확신할 수 없다면 오버로딩이나 보편 참조를 사용하면 된다.

이번 항목에서 다뤘던 예제에 한해서는 값 전달 방식이 1회의 이동 연산만 추가되었지만 연쇄적으로 값 전달 방식 함수 호출이 여러번 이루어진다면 연산 횟수가 누적되어 비용이 상당히 커질 수 있다.

 

성능과 무관하게 값 전달은 잘림 문제(slicing problem)가 발생할 여지가 있다.

함수가 기반 클래스 타입이나 파생 클래스 타입을 매개변수로 받는 경우 해당 매개변수는 값 전달 방식으로 선언하지 않는 것이 좋다.

 

◾ 이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달이 참조 전달만큼이나 효율적이고, 구현하기가 더 쉽고, 산출되는 목적 코드의 크기도 더 작다.

◾ 왼값 인수의 경우 값 전달(즉, 복사 생성) 다음의 이동 대입은 참조 전달 다음의 복사 대입보다 훨씬 비쌀 가능성이 있다.

◾ 값 전달에서는 잘림 문제가 발생할 수 있으므로, 일반적으로 기반 클래스의 매개변수 타입에 대해서는 값 전달이 적합하지 않다.

+ Recent posts