auto

auto는 C++11에서 등장한 문법으로, 변수를 생성하는 방법 중 하나이다.
변수의 자료형을 컴파일러가 타입 추론 방식을 통해 결정하기에 선언과 동시에 초기화를 하여야한다.


1. 변수 선언 및 초기화 예시

auto a; // error, 선언만 할 경우 타입 추론이 불가

int b = 1, c = 2;
auto d = b + c; // 변수 선언과 동시에 초기화

string s = "123";
auto str0 = s; // string str0
auto str1 = "hello, world"; // const char *str1
auto i = 30; // int i


2. 포인터

class Test{
private:
    string my_str;
public:
    Test(string& s) : my_str(s){}
    ~Test(){}
    void print(){cout << my_str << endl;}
};

int main(){
    string s = "hello, world!";
    Test t(s);

    auto auto1 = t;
    auto* auto2 = &t;
    auto auto3 = &t;

    auto1.print(); // hello, world!
    auto2->print(); // hello, world!
    auto3->print(); // hello, world!
}

포인터 역시 auto를 통해 받아 올 수 있다.
이때 auto2와 auto3는 모두 포인터 형식 자료형인데, 선언 시 auto와 auto의 차이점은 무엇일까

기능적으로 말하면 차이점은 없다. auto나 auto
나 초기화 시 포인터를 넘겨주면
컴파일러가 데이터 타입을 추론하기 때문이다. 그럼에도 auto*를 쓰는 이유는 아래 코드를 예시로 설명하겠다.

auto a = testFunc();

testFunc()의 반환값을 모르는 상태에서 위 코드만 보았을 때
a의 자료형이 단순 값인지, 포인터값인지 알 수가 없다. 즉 가동성이 떨어지기에 auto*를 사용한다.

3. 참조자

참조자 역시 auto로 사용할 수 있는데 아래 코드를 살펴보자.

class Test{
private:
    string my_str;
public:
    Test(string& s):my_str(s){}
    ~Test(){}
    void print(){cout << my_str << endl;}
};

int main(){
    string str("hello world");
    Test t1(str);
    Test& t2 = t1; // t1 참조
    
    auto auto1 = t2;
    cout << &t1 << endl; // 0x7ff7b7623310
    cout << &t2 << endl; // 0x7ff7b7623310
    cout << &auto1 << endl; // 0x7ff7b76232e0
}

주석 처리한 output에서 보이는 것처럼, auto1은 t1을 참조한 t2를 참조한 값이 아닌 것을 알 수 있다.
참조의 성공하였다면 같은 주소값을 출력하여야 하기 때문이다.

이는 포인터와는 다르게 참조자에선 auto와 auto&가 기능적으로 다르게 적용된다는 것을 알 수 있다.
아래 코드는 올바르게 auto를 사용하여 참조하는 방법이다.

auto& auto1 = t2;
cout << &t1 << endl; // 0x7ff7bb6fd310
cout << &t2 << endl; // 0x7ff7bb6fd310
cout << &auto1 << endl; // 0x7ff7bb6fd310


4. 가독성, iterator

auto는 자료형을 직접 작성하지 않아도 된다는 장점이 있지만,
가독성을 무너뜨린다는 큰 단점이 존재한다. 이는 코드가 길어질 수록 치명적 단점이 될 수 있다.

따라서 auto를 사용할 때는 auto라 할 지라도 가독성이 뛰어난 경우, 단점 없이 장점만 요긴하게 사용할 수 있다.
auto 사용이 이득이 되는 가장 좋은 예시로는 iterator가 있다.

vector<int> test_v = {1,2,3,4,5};

for (vector<int>::iterator iter = test_v.begin(); iter != test_v.end(); iter++){~~} // a
for (auto iter = test_v.begin(); iter != test_v.end(); iter++){~~} // b

a의 코드가 기본적으로 iterator를 사용하여 벡터를 순회하는 방식이다.
iter 선언 시 코드가 상당히 길어진다. 이를 auto로 사용하면 전체적으로 훨씬 간결해지고
auto로 선언한 변수가 가리키는 값이 무엇인지 확실하기에, 가독성 면에서도 단점이 없다. (begin, end iterator 메소드를 통해 유추 가능)




auto는 자료형을 컴파일러가 추론하기에 편리하지만, 코드 가독성상 단점이 존재한다.
iterator와 같은 경우를 제외하고는 기존 자료형으로 명시하는 것이 좋다.