728x90

특징

- 일종의 조커카드로 타입을 명시적으로 지정하지 않아도 타입을 추론 가능하게 하는 타입이다. 이를 형식추론 혹은 형식연역(type deduction, type inference)

- c+14 에서 함수 리턴값으로 auto 키워드가 사용 가능

 

주의

const나 &가 붙은 타입은 auto로 정확하게 타입추론이 되지 않는다. 보통 const나 &를 무시한 나머지 타입을 근거로 추론하는 것 같다.

- 함수 매개변수 타입으로 사용 불가능

- auto 키워드를 사용하여 선언할 경우에 반드시 초기값이 있어야 한다.

 

사용 예제

#include <iostream>
#include <vector>

class Knight
{

};

// 함수 매개변수 타입으로 쓰인 경우 -> 컴파일 에러.
int Func(auto num1, auto num2) 
{

}

// 함수 리턴 타입으로 auto가 사용 가능.(c++14부터)
auto Func1(int num1, int num2)
{
    return num1 + num2;
}

int main()
{
    // 정상적으로 함수 추론이 가능한 경우.
    {
        auto a = 3; // int
        auto b = 3.14f; // float
        auto c = 1.23; // double
        auto d = Knight(); // class
        auto e = "rookiss"; // string literal
        auto f = Func1(1, 2); // int
    }

    // 컴파일 에러. auto 타입 사용시 반드시 초기값이 필요하다.
    {
        auto a; 
    }

    // const와 &가 포함된 타입은 정확하게 추론하지 못 하는 auto.
    {
        int a;
        int& ref = a;
        const int cst = a;

        auto refAuto = ref; // int 
        auto cstAuto = ref; // int
    }

    // auto 특성을 제대로 이해하고 사용된 예
    {
        std::vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);

        for (size_t i = 0; i < v.size(); i++)
        {
            //!주의! 이 경우 여전히 int 타입으로 추론합니다. 
            //auto data = static_cast<int&>(v[i]); 

            auto& data = v[i]; // int&
            data = 100;
        }
    }

    // stl literator에 사용된 auto
    {
        std::vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);

        // auto 사용 이전.
        for (std::vector<int>::iterator it = v.begin(); it < v.end(); it++)
        {
            *it = 1;
        }

        // auto 사용 후.
        for (auto& it : v)
        {
            it = 1;
        }
    }
}

 

결론

- 특별하게 타입이 길지 않는 이상 명시적으로 타입을 지정하는게 가독성 측면에서 좋은 것 같다.

- stl literator 같은 경우 사용 빈도가 높고 타입이 길어지는 경우가 많으니 auto를 사용하기 적합

- 태블릿과 같이 고급기법에서 auto가 활용 되기도 한다.

 


공부한 내용을 개인적으로 복습하기 쉬운 형태로 정리했습니다.

잘못된 내용이 있는 경우 언제든 댓글 혹은 이메일로 지적해주시면 감사드립니다.

 

728x90

'Programming Language > C++' 카테고리의 다른 글

{} 중괄호 초기화  (0) 2022.02.06
using - type alias declaration  (0) 2022.02.06
함수 객체(함수자, Functor)  (0) 2022.02.06
함수 포인터  (0) 2022.02.06
2-2_입력( <<,manipulator,조정자)  (0) 2021.12.07
728x90

특징

- 함수처럼 동작하는 객체

- () 연산자 오버로딩을 통해 함수와 비슷한 형태로 호출합니다.

- 함수자(Functor) 라고도 합니다.

 

함수 포인터와 함수 객체의 차이점

- 함수 포인터는 시그니처가 일치 하지 않으면 전달할 수 없지만 함수 객체는 객체를 전달하기 때문에 상관 없다.

- 함수 포인터는 정해진 시그니처에 맞게만 호출 가능하지만 함수 객체는 ()연산자 오버로딩을 통해 다양한 시그니처로 호출될 수 있다.

- 함수 객체는 객체의 특성을 모두 갖기 때문에 상태를 저장(람다에서의 캡쳐와 동일)할 수 있다.

- 함수 객체는 인라인 함수로 호출될 수 있습니다.

 

함수 객체 간단한 구현 예제

- 함수 객체는 () 오버라이딩과 객체기 떄문에 멤버변수를 갖을 수 다는 특징이 있습니다.

#include <iostream>
using namespace std;

class Functor
{
public:
    void operator() (int data)
    {
        _data = data;
        cout << "void (void) : " << _data << endl;
    }

    int operator() (int a, int b)
    {
        cout << "int (int, int)" << endl;
        return a + b;
    }

public:
    int _data;
};

int main()
{   
    Functor functor1;
    functor1(10);

    Functor functor2;
    cout << functor2(1, 2) << endl;

    return 0;
 }

 

함수 객체만 인라인 함수 호출이 가능하다는 건 무슨 얘기일까?

함수 객체는 ()연산자 함수를 inline으로 호출되게 할수 있습니다. 이 말인 즉슨 일반 함수 포인터를 사용하게 되면 해당 함수는 inline방식으로 호출할 수 없습니다.

 

여기서 inline 함수란 뭘까요? 인라인함수를 사용하면 컴파일 타임에 코드가 매크로와 같이 치환됩니다. 따라서 함수를 호출하는 오버헤드가 없지만, 소스코드양이 증가합니다. 따라서 코드가 짧고 자주 호출되는 함수를 inline으로 지정하는 것이 효율적입니다. 하지만 요즘 컴파일러는 최적하여 inline 적용 여부를 정합니다. 이떄 __forceiline 키워드를 이용하여 컴파일러에게 inline을 강제할 수도 있긴 하지만 이 경우에도 컴파일러가 판단하기에 비효율적이면 무시될 수도 있습니다.

 

그렇다면 왜 함수 객체를 이용하면 inline 사용을 컴파일러에게 유도할 수 있는 것일까요?

이는 inlline 적용 여부가 컴파일 단계에서 결정 되기 때문에 inline 시킬 대상(코드로 치환할)이 명확하게 컴파일러가 알수 있어야 합니다.

하지만 함수 포인터를 이용한 호출의 경우 시그니처가 동일한 함수가 여러개 정의 되어 있으면 컴파일러는 어떤 코드를 치환해야 할지 알수 없습니다.

 

아래 코드 실행시 생성되는 어셈블러 코드를 확인 하면 함수포인터로 호출된 함수는 무조건 일반함수 호출을 하는 것을 알 수 있습니다.

#include <iostream>
using namespace std;

void PFunc1()
{
    cout << "PFunc1" << endl;
}

inline void PFunc2()
{
    cout << "PFunc2" << endl;
}

class Functor
{
public:
    void operator() ()
    {
        cout << "Functor()" << endl;
    }

    inline void operator() (int num)
    {
        cout << "Functor(int)" << endl;
    }

};

int main()
{
    void (*pfunc)(void);
    pfunc = &PFunc1;
    pfunc(); // 일반함수 -> 일반함수 방식으로 호출(항상)

    pfunc = &PFunc2;
    pfunc(); // 인라인함수 -> 일반함수 방식으로 호출(항상)

    Functor functor;
    functor(); // 일반함수 -> 인라인 함수로 호출(컴파일러가 판단)
    functor(1); // 인라인 함수 -> 인라인 함수로 호출(컴파일러가 판단)

}

 

함수 객체의 의미

- 일감을 객체 형태로 정의(데이터, 로직)하고 실제 일감은 다른 로직을 통해 적절한 시기에 일감이 처리 되도록 할수 있다. 이는 일감을 정의하는 시점과 일감을 처리하는 시점을 구분할 수 있다는 것을 의미(커맨더 패턴) 하는 중요하는 개념이다.

- 템플릿과 같이 사용되어 콜백 함수 구현 등에 활용 된다.

 

콜백함수 구현 예제

#include <iostream>
using namespace std;

class Item
{
public:
    int _itemId = 0;
    int _rarity = 0;
    int _ownerId = 0;
};

//함수 객체
class FindByOwnerId
{
public:
    bool operator() (const Item* item)
    {
        return (item->_ownerId == _ownerId);
    }

public:
    int _ownerId;
};

//함수 객체
class FindByRarity
{
public:
    bool operator() (const Item* item)
    {
        return (item->_rarity == _rarity);
    }

public:
    int _rarity;
};

template<typename T>
Item* FindItem(Item items[], int itemCount, T selector)
{
    for (int i = 0; i < itemCount; i++)
    {
        Item* item = &items[i];

        if (selector(item))
        {
            return item;
        }
    }

    return nullptr;
}

int main()
{       
    Item items[10];
    items[3]._ownerId = 100;
    items[8]._rarity = 2;

    FindByOwnerId functor1;
    functor1._ownerId = 100;

    FindByRarity functor2;
    functor2._rarity = 2;

    Item* item1 = FindItem(items, 10, functor1);
    Item* item2 = FindItem(items, 10, functor2);

    return 0;
 }

결론

- 함수 객체(함수자)가 일반함수포인터를 사용하는 경우보다 장점이 훨씬 많습니다.

- 인라인 함수 호출 유도, 상태 저장, 템플릿과 궁합이 잘 맞는 등..

- STL에서 함수객체를 받는 이유도 inline 함수 호출을 유도하여 라이브러리단의 기능을 최적화 하려는 것으로 볼 수 있다.


공부한 내용을 개인적으로 복습하기 쉬운 형태로 정리했습니다.

잘못된 내용이 있는 경우 언제든 댓글 혹은 이메일로 지적해주시면 감사드립니다.

 

728x90

'Programming Language > C++' 카테고리의 다른 글

using - type alias declaration  (0) 2022.02.06
auto  (0) 2022.02.06
함수 포인터  (0) 2022.02.06
2-2_입력( <<,manipulator,조정자)  (0) 2021.12.07
C++/StackPadding  (0) 2021.11.23
728x90

선언방법

int (*pfunc1)(int, int);   // 일반적인 방법         
typedef int (*pfunc2)(int, int); // 편의를 위해 typdef를 이용하여 별칭을 정의
using pfunc3 = int(*)(int, int); // 편의를 위해 using을 이용하여 별칭을 정의

 

정의

함수 이름은 배열이름과 같ㅌ이 함수의 위치를 가리키는 주소값을 나타낸다. (실제론 &를 사용해서 함수를 참고해야 하는 경우도 있으니 절대적이진 않다.)

따라서 함수포인터 변수를 통해 동일한 시그니처의 함수를 전달할 수 있다.

 

사용법

- 1. 함수 포인터 변수 = [함수이름]

- 2. 함수 포인터 변수 = &[함수이름]

- 1. 함수이름();

- 2. (*함수이름)();

#include <iostream>

int func(int a, int b)
{
    return a + b;
}

int main()
{
    int (*pfunc)(int, int);

    pfunc = func; // 방식1.배열이름과 같이 함수이름이 주소값을 나타낸다. 하지만 이는 C언어와의 호환성 때문에 컴파일러에서 지원 하는 기능이다.
    pfunc = &func; // 방식2. 위 방식보다 이게 FM 방식이다. 클래스 멤버함수를 변수에 전달할 때는 FM 방식을 따라야 하는 경우가 있다.
    
    pfunc(1,2); // 방식1.편의
    (*pfunc)(1, 2); // 방식2.FM
}

 

함수 포인터 변수가 아닌 일반 함수 변수는 사용하지 않을까?

- 함수 변수는 단순히 함수 선언부이다. 따라서 linking 과정에서 같은 시그니처(이름 포함) 정의부현을 찾아서 해당 함수가 호출된다.

- 변수에 다른 함수를 넘길 수 없다.

#include <iostream>
using namespace std;

int func(int a, int b)
{
    cout << "func()" << endl;
    return a + b;
}

int func1(int a, int b)
{
    cout << "func1()" << endl;
    return a + b;
}


int main()
{
    int (func)(int, int); // 일단적인 타입과 다르게 메모리에 적재 되지도 않음. 단순히 함수 선언부이다.

    {
        func = func1; // (x)
    }

    {
        func(); // func 함수 정의가 있다면 호출. 없다면 링킹에러.
    }
 }

따라서 아래와 같은 방식으로 사용된다.

#include <iostream>
using namespace std;

int func(int a, int b)
{
    return a + b;
}

int main()
{       
    // 선호 되는 방식
    {
        typedef int (*pfunc1)(int, int); 
        using pfunc2 = int(*)(int, int); 

        pfunc1 p1 = func;
        pfunc2 p2 = func;
    }

    // 비선호 되는 방식
    {
        typedef int (pfunc1)(int, int);
        using pfunc2 = int(int, int);

        pfunc1* p1 = func;
        pfunc2* p2 = func;
    }
 }

 

예외적인 사용법 - 멤버함수를 보관하는 함수

위와 같은 방식으로(전역, 정적 함수만 가능) 특정 클래스의 비정적 멤버함수를 변수에 받을 수 없다.

비정적 멤버함수를 변수로 받을 때는 전달방식, 호출방식 모두 차이가 있으니, 아래 코드를 통해 확인 하자.

#include <iostream>
using namespace std;

class Knight
{
public:
    int func(int a, int b)
    {
        cout << "Knight:func()" << endl;
        return a + b;
    }
};

int main()
{       
    int (Knight::*pmfunc)(int, int);

    {
        pmfunc = Knight::func; // 일반적인 방식으로 전달 불가.(컴파일 에러)
        pmfunc = &Knight::func; // FM으로 &연산자 사용 해야 함.
    }

    {
        Knight k1;

        k1.pmfunc(1, 2); // 불가
        (k1.*pmfunc)(1, 2); // 가능(FM)
    }

    {
        Knight* k1 = new Knight();
        (k1->pmfunc)(1, 2); // 불가
        (k1->*pmfunc)(1, 2); // 가능(FM)
    }
    
 }

 


공부한 내용을 개인적으로 복습하기 쉬운 형태로 정리했습니다.

잘못된 내용이 있는 경우 언제든 댓글 혹은 이메일로 지적해주시면 감사드립니다.

728x90

'Programming Language > C++' 카테고리의 다른 글

auto  (0) 2022.02.06
함수 객체(함수자, Functor)  (0) 2022.02.06
2-2_입력( <<,manipulator,조정자)  (0) 2021.12.07
C++/StackPadding  (0) 2021.11.23
c/c++/size_t,intptr_t,uintptr_t  (0) 2021.11.23
728x90

1.<<연산자

inserstion? 밀어넣기? 출력, 푸쉬 연산자?

+,-등과 같은 연산자 중 하나

c++에서는 프로그래머가 연산자 동작을 바꿀 수 있다 => 연산자 오버로딩

 

2.출력형식 지정 (Output formatting)

C vs C++

 

C

서식문자를 이용하여 출력서식 지정

 int num =10;
 printf("%#x\n", number);

 

C++

manipulator를 이용한 출력서식 지정

int number = 10;
cout << showbase << hex << number << endl;

 

C++의 Manipulator(조정자) 종류

* 조정자를 이용하여 출력서식 지정시 설정된 값이 이후 출력에도 적용되는 경우가 있으므로

출력서식을 기본값으로 바꿔놓는 걸 유념하기

 

기본 조정자

 

showpos/noshowpos

-pos는 positive를 의미하며 양수 기호를 출력할지 여부를 결정합니다.

int number = 123;
cout << showpos << number << endl;    //+123
cout << noshowpos << number << endl;  //123

number = -123;
cout << showpos << number << endl; //-123
cout << noshowpos << number << endl; //-123

 

dec/hex/oct

- 숫자 출력 진법을 지정합니다.

int number = 123;
cout << dec << number << endl; //123
cout << hex << number << endl; //7b
cout << oct << number << endl; //173

 

uppercase/nouppercase

- 대소 출력을 지정합니다(문자에는 지정되지 않습니다.)

int number = 123;
cout << uppercase << hex << number << endl; //7B
cout << nouppercase << hex << number << endl; //7b
char c = 'A';
cout << nouppercase << c << endl; // A ---- 문자에는 적용되지 않음

 

showbase/noshowbase

- 진법 base 출력을 지정합니다.

int number = 123;
cout << showbase << hex << number << endl; //0x7b
cout << noshowbase << hex << number << endl; //7b

 

showpoint/noshowpoint

- 유효한 소수점 자리까지만 출력할지 여부를 지정합니다.

float decimal1 = 100.0f;
float decmall2 = 100.12f;
cout << noshowpoint << decimal1 << " " << decmall2 << endl; //100 100.12
cout << showpoint << decimal1 << " " << decmall2 << endl; //100.000 100.120

 

fixed/scientific

- 소수점 표기 방법을 구분 합니다.

float number = 123.3456789f;
cout << fixed << number << endl; //123.345680
cout << scientific << number << endl; //1.233456e+02

 

boolalpha/noboolalpha

- boolean자료형 출력 형태를 지정합니다.

bool bReady = true; 
cout << boolalpha << bReady << endl; //true
cout << noboolalpha << bReady << endl; //1

 

iomanip.h에 정의되어 있는 조정자

setw()

- 정렬 형태를 지정합니다.

int number = -123;
cout << setw(6) << left << number << endl; //[-123  ]
cout << setw(6) << internal << number << endl; //[-  123]
cout << setw(6) << right << number << endl; //[  -123]

 

setfill()

- 빈공간을 지정한 문자로 채운다.

int number = 123;
cout << setfill('*') << setw(5) << number << endl; //[**123]

 

setprecision()

- 유효숫자 정확도 수준을 지정.

float number = 123.456789f;
cout << setprecision(7) << number << endl; //123.4568
cout << setprecision(10) << number << endl; //123.4567871
728x90

'Programming Language > C++' 카테고리의 다른 글

함수 객체(함수자, Functor)  (0) 2022.02.06
함수 포인터  (0) 2022.02.06
C++/StackPadding  (0) 2021.11.23
c/c++/size_t,intptr_t,uintptr_t  (0) 2021.11.23
C++/함수호출규약/__cdecl  (0) 2021.11.23
728x90

728x90
728x90

size_t

-크기를 의미할 떄 주로 사용되는 자료형이다.

-운영체제 환경에 맞춰 가변적인 자료형을 대응하기 위해 typedef 된 자료형이다.

 

 

intptr_t, uintptr_t

-포인터의 주소를 저장하는데 사용 된다.

-다른 환경으로 이식이 가능하고, 안전한 포인터 선언 방법을 제공한다.

-시스템 내부에서 사용하는 포인터와 같은 크기다.

-포인터를 정수로 표현할 때 유용하게 사용 가능하다.

728x90
728x90

 

 

Argument Passing and Naming Conventions

Learn more about: Argument Passing and Naming Conventions

docs.microsoft.com

 

__cdecl

visual c/c++ 컴파일러에서 지원하는 calling convetion의 하나이다.

키워드 __cdecl을 사용하게 되면 stack cleanup 주체는 caller이고 

스택에 매개변수를 push 할때 매개변수 반대 순서로 스택에 저장하게 된다.

 

이외에도 다양한 calling convetion을 지정하는 키워드 들이 있다.

 

아래와 같이 visual studio에서 default calling covention을 설정하게 되면 calling convention을 생략하고 사용 가능하다.

 

아래와 같이 메인함수 지역변수 a,b는 주소값이 감소하는 반면

매개변수 aa,bb는 주소값이 증가하는 것을 확인할 수 있다.

 

하지만 64bit에서는 그렇지 않다!

 

이는 컴파일러가 64bit에서 스택프레임을 관리하는 방식이 다르기 때문인데 자세한 내용은 아래 문헌을 참고하자.

 

x64 stack usage

Learn more about: x64 stack usage

docs.microsoft.com

 

결론

__cdcel은 calling convertion을 지정하는 키워드 중의 하나로 __cdcel로 지정된 함수의 매개변수는 뒤에서 부터 스택에 쌓인다. 기본적으로 visual studio에 default convetion으로 등록되어 있다.

 

 

참고

 

C언어 코딩 :: 함수호출규약 __cdecl

C언어의 함수호출규약(function Calling Convention)에 대해 이해합니다. __cdecl __stdcall __fastcall...

blog.naver.com

 

Argument Passing and Naming Conventions

Learn more about: Argument Passing and Naming Conventions

docs.microsoft.com

 

728x90

'Programming Language > C++' 카테고리의 다른 글

C++/StackPadding  (0) 2021.11.23
c/c++/size_t,intptr_t,uintptr_t  (0) 2021.11.23
2-1_메인함수 훑어보기 namespace,using,#pragma once  (0) 2021.11.23
1-3_C++를 공부해야 하는 이유  (1) 2021.11.23
1-2_C++의 활용  (0) 2021.11.23
728x90

std::

C++표준 이름공간 즉, namespace이다

 

using

using 자바의 import와  c#의 using과 비슷

타이핑 양을 줄인다.

 

#pragma once

링크 과정에서 header파일이 순환 참조 되는걸 막아준다.

 

<< insertion operator, 출력연산자

+,-와 같은 연산자 중 하나

c++에서는 프로그래머가 연산자의 동작을 바꿀 수 있다.

 

cout << "hi, banana" << endl;
cout << "hi, apple" << endl;
cout << "hi" << endl << "hello" << endl;  << 이와 같이도 사용 가능

 

728x90

+ Recent posts