필요없어진 C++ 클래스 삭제하기

 

작성버전 :: 4.20.3

 

처음부터 끝까지 설계가 완벽하고 수정할 일이 없다면 그럴 일이 없겠지만, 코드 작업을 하다보면 기존에 있던 클래스를 삭제해야하는 일이 가끔 발생한다. 특히 아직 프로토타입 작업을 하는 과정이라면 작성해둔 클래스가 필요없어져서 삭제해야하는 일이 생각보다 자주 발생할 수 있다.

 

 

 

하지만 위의 이미지와 같이 간단하게 삭제할 수 있는 블루프린트 클래스와 달리 C++ 클래스는 에디터 내에서 삭제할 수 있는 방법이 존재하지 않는다. 그렇다고 더이상 사용하지 않게된 C++ 클래스를 무작정 쌓아두고 있을 수만은 없는 법이다.

 

 

 

1. 필요 없어진 C++ 클래스를 삭제하기 전에 에디터를 닫는다.

 

 

 

2. 비주얼 스튜디오로 가서 솔루션 탐색기에서 지우고자 하는 클래스의 헤더(.h)와 소스파일(.cpp)를 선택한 뒤 제거한다.

 

 

 

3. 프로젝트 폴더의 Source 폴더 안에 남아있는 클래스의 헤더(.h)와 소스파일(.cpp) 역시 삭제해준다.

 

 

4. 비주얼 스튜디오로 돌아가서 [빌드 > 솔루션 다시 빌드]를 선택해서 프로젝트를 다시 빌드한다.

 

 

 

5. 프로젝트 빌드가 성공적으로 끝났다면 에디터를 다시 실행시킨다. 그렇게 하고 콘텐츠 브라우저를 보면 필요없는 C++ 클래스가 성공적으로 삭제된 것을 확인할 수 있다.

 

 

주의사항

 

블루프린트 클래스는 관련되어 있거나 레퍼런스가 있는 상태라면 삭제하기 전에 경고창을 띄워주고 정말로 삭제할 것인지 확인을 하지만, C++ 클래스는 그런 과정이 없기 때문에 지우고자하는 클래스가 레벨에 배치되어있는지, 다른 곳에서의 레퍼런스가 있는지, 또는 다른 클래스에서 헤더를 포함시켜서 사용하고 있는 것은 아닌지 신중하게 확인하고 삭제하는 것이 좋다.

 

또한 필요없어진 C++ 클래스를 삭제함으로서 신텍스 에러가 발생한다면 4번 과정에서 프로젝트를 리빌드가 실패하게 될 것이다. 그렇기 때문에 클래스를 삭제한 뒤에 오류목록을 살펴서 클래스를 삭제한 여파로 발생한 에러가 없는지 확인하는 과정 역시 필요하다.

 

 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

C++ / USTRUCT 사용자 정의 구조체 만들기

 

작성버전 :: 4.20.3

 

구조체는 기존에 존재는 데이터 타입을 조합하여 새로운 데이터 타입을 만들어내는 유용한 개념이다.

 

struct UserDefinedStruct

{

public:

    int i;

    float f;

};

 

일반적인 C++ 프로젝트에서는 구조체를 위와 같이 정의하고 사용하게 된다.

 

하지만 언리얼 엔진 프로젝트에서 이러한 정규 구조체는 C++ 코드 내부에서는 사용될 수 있지만, 에디터의 디테일 패널에 노출되지 않고, 블루프린트에서도 사용이 불가능하다.

 

에디터에서 사용가능한 구조체를 만들고자 한다면 언리얼 구조체 즉, USTRUCT를 만들어야 한다.

 

 

블루프린트에서만 사용할 구조체라면 위의 이미지와 같은 방법으로 구조체를 생성할 수 있는데, 블루프린트 구조체는 C++ 코드에서는 사용할 수 없다. 하지만 C++ 코드에서 만든 구조체는 C++ 코드는 물론 블루프린트에서도 사용할 수 있다는 장점이 있다.

 

C++ 언리얼 구조체는 간단한 블루프린트 구조체 생성 방법과 비교했을 때, 엔진 내부에서 명시적인 생성 방법이 없기 때문에 생성 과정이 조금 복잡하다.

 

 

언리얼 구조체 만들기

 

언리얼 구조체를 만드는 과정을 따라가보도록 하자.

 

 

 

 

 

우선 사용자가 정의한 UStruct를 담을 헤더를 만들어야 한다. 만약 구조체가 특정한 클래스에서만 자주 사용될 것이라면 그 클래스의 헤더 파일 하단에 구조체를 정의하는 편이 좋지만, 범용적으로 여러 곳에서 사용될 구조체라면 사용자가 정의한 헤더에 몰아서 정의하는 편이 좋다.

 

CustomStruct00 클래스의 추가가 끝났다면 아래의 예시 코드와 같이 클래스 정의 아래 쪽에 커스텀 구조체를 정의해보자.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CustomStruct00.generated.h"

UCLASS()
class CUSTOMSTRUCTTEST_API ACustomStruct00 : public AActor
{
    GENERATED_BODY()
   
public:   
    // Sets default values for this actor's properties
    ACustomStruct00();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:   
    // Called every frame
    virtual void Tick(float DeltaTime) override;
};


USTRUCT(Atomic, BlueprintType)
struct FCustomStruct
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
        AActor* actor;
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
        float f;
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
        int32 i;
};

 

클래스에는 UCLASS() 매크로가 붙지만 구조체의 경우에는 USTRUCT() 매크로가 붙는다. 그리고 구조체 지정자는 Atomic과 BlueprintType으로 지정해뒀는데 Atomic은 이 구조체가 항상 하나의 단위로 직렬화(Serialize)됨을 의미하고 BlueprintType은 이 구조체가 블루프린트에서 사용될 수 있음을 의미한다.

 

만약 이 구조체가 에디터의 디테일 창에서 표시되고 수정 가능하기만 원한다면 지정자를 Atomic으로만 설정하기를 권한다. 또한 모든 멤버 변수의 UPROPERTY() 매크로의 지정자를 EditAnywhere로 설정해야 한다.

 

혹은 구조체가 디테일 창에서는 보이지 않고 코드 내부나 블루프린트에서만 사용되기를 원한다면 USTRUCT() 매크로의 지정자를 BlueprintType으로, UPROPERTY() 매크로의 지정자를 BlueprintReadWrite로 설정해야 한다.

그리고 구조체의 이름은 F로 시작되게 작성해야 하며, 댕글링(Dangling) 포인터 문제에 대해서 보호받기 위해서 구조체의 모든 멤버 변수들에 UPROPERTY() 매크로를 붙이는 것을 권장한다.

 

또한 구조체의 멤버 변수에 포인터를 사용한다면 깊은 복사 얕은 복사 문제에 주의를 기울여야 한다.

 

사용할 구조체를 모두 정의했다면, 이 구조체를 사용할 코드의 헤더에 CustomStruct00.h를 포함시켜준다.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CustomStruct00.h"
#include "TestActor.generated.h"

UCLASS()
class CUSTOMSTRUCTTEST_API ATestActor : public AActor
{
    GENERATED_BODY()
   
public:   
    // Sets default values for this actor's properties
    ATestActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:   
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere)
    FCustomStruct st;
};

 

 

 

이렇게 구조체를 테스트 액터의 멤버 변수로 추가시킨 후 에디터로 돌아가서 컴파일을 해주고, 액터를 레벨에 배치하고 선택해보면 위의 이미지처럼 구조체가 디테일 패널에서 수정가능하록 노출된 것을 확인할 수 있다.

 

Tip :: 이후에 구조체의 멤버 변수 종류를 수정하고 컴파일했을 때, 디테일 패널에 곧바로 적용이 되지 않는 문제가 가끔있는데 이런 경우 해당 구조체를 가진 클래스의 멤버 변수에 임시 변수 하나를 추가하고 컴파일하면 적용이 된다.

 

 

 

 

 

생성한 구조체 블루프린트에서 사용하기(Use Custom Struct at Blueprint)

 

C++ 코드에서 정의한 구조체를 블루프린트에서 사용하는 방법은 간단하다.

 

 

 

블루프린트에서 정의한 CustomStruct 변수 유형으로 변수를 추가할 수 있다.

 

 

 

이미지와 같이 이벤트 그래프에서 우클릭을 한 뒤 정의한 구조체의 이름을 검색하면 이벤트 플로우 도중에 CustomStruct를 만들거나 구조체를 분해해서 구조체의 변수를 따로 뽑아내서 사용할 수도 있다.

 


 

참고

 

Unreal Engine 4 Wiki :: Structs, USTRUCTS(), They're Awesome(https://wiki.unrealengine.com/Structs,_USTRUCTS(),_They%27re_Awesome)

Unreal Engine 4 Wiki :: How To Make UStruct(https://wiki.unrealengine.com/How_To_Make_UStruct)

 

 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

프로그래밍 작업시 헤더(Header) 포함(Include) 문제

 

작성버전 :: 4.20.3

 

언리얼 엔진4(이하 언리얼 엔진 혹은 언리얼)에서의 프로그래밍은 C++기반으로 구성되어 있기 때문에 다른 헤더 파일에 정의된 클래스 등을 사용하기 위해서는 헤더를 포함하는 전처리가 필수적이다.

 

일반적인 C++ 프로젝트에서 헤더가 꼬이거나 중복 호출되는 경우만 조심하면 주의하면 되고 헤더의 순서는 별로 중요하지 않은 것에 비해서 언리얼에서의 헤더 포함은 약간 복잡하고 귀찮은데다가 버그까지 있다.

 

언리얼 엔진이 아닌 다른 C++ 프로젝트에서는 프로그래밍 작업 도중에 필요한 헤더가 생긴다면 상단의 전처리기들 아래에 필요한 헤더의 포함 선언을 추가할 것이다. 하지만 언리얼에서는 헤더 포함의 위치가 중요하다.

 

 

 

만약 AMyActor 클래스에서 AMyActorComponent를 사용하기 위해서 MyActorComponent.h의 선언을 일반적인 C++ 프로젝트에서 하듯이 MyActor.h의 헤더 포함 리스트 제일 아래에 추가하면 위의 이미지처럼 신텍스 에러(E0077 this declaration has no storage class or type specifier)가 발생하고 에디터에서의 컴파일 역시 실패한다.

 

언리얼에서는 다른 헤더를 포함할 때, 항상 generated.h 헤더보다 위쪽에 포함 선언을 추가해야 된다.

 

 

 

위의 이미지처럼 새롭게 추가하는 헤더 포함 선언을 generated.h 헤더 선언 위로 올리고 수정한 소스 파일을 저장하면 대부분은 신텍스 에러가 사라진다. 하지만 여기서 고질적인 문제가 발생하는데 꽤나 높은 빈도로 정상적으로 generated.h 위쪽으로 헤더 포함 선언을 옮겼는데도 불구하고 아래의 이미지처럼 신텍스 에러가 사라지지 않는 경우가 있다.

 

 

 

이러한 문제는 인텔리전스 버그로 실제 컴파일에서는 전혀 문제가 되지 않는다. 실제로도 에디터에서 컴파일을 해보면 전혀 문제 없이 컴파일이 진행되는 것을 알 수 있다. 이런 인텔리전스 버그는 잠시 후에 없어지기도 하고, 에디터에서 컴파일하거나, 비주얼 스튜디오나 언리얼을 재실행 하는 것으로도 없어진다.

 

이런 헤더 순서 문제가 매우 중요함에도 불구하고 언리얼 엔진 레퍼런스 문서에서는 쉽게 찾을 수 없는게 문제다.

 

 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

enum class

 

인간은 문자와 기호 그리고 숫자를 이용하고 컴퓨터는 모든 것을 숫자로 받아들인다. 그렇기 때문에 인간이 알아보기 쉽게 문자열로 만들면 프로그램이 느려지고 컴퓨터가 알아보기 쉽게 숫자로 만들면 인간이 알아보기가 어려워진다. 이것을 해결하기 위한 방법의 하나가 바로 enum(열거형)이다.

 

이 enum은 일반적으로 이름을 가지는 정수형의 상수를 정의해서 알아보기 쉽게 해주는데, 이 기존의 enum은 몇가지의 문제점이 존재한다.

 

 

1. 열거형에 존재하는 이름과 같은 이름의 클래스를 선언할 수 없다.

일반적인 enum 내에 선언된 열거자의 이름은 전역적이다. 그렇기 때문에 enum 내부에 선언된 이름과 같은 이름을 가진 클래스를 선언하고 사용하려고 하면 컴파일 에러가 발생한다. 다음의 코드는 이것에 대한 예시이다.

 

#include <iostream>
using namespace std;

enum EColor
{
    Red,
    Green,
    Blue,
};

class Red { };

int main()
{
    Red r;    // 컴파일 에러가 발생한다.
    EColor color = Red;

    return 0;
}

 

위와 같은 일이 발생하는 이유는 MSDN에 다음과 같이 설명되어 있다.

 

enum EColor
{
    Red,
    Green,
    Blue,
};

 

위와 같이 정의된 enum의 의미 체계는 다음과 같다고 한다.

 

static const int Red = 0;
static const int Green = 1;
static const int Blue = 2;

 

다시 말하자면 이미 전역 변수에 같은 이름의 변수가 선언되어 있기 때문에 같은 이름의 클래스를 선언할 수 없는 것이다. 하지만 enum class로 정의된 열거형의 경우에는 위와 같은 문제가 발생하지 않는다.

 

#include <iostream>
using namespace std;

enum class EColor
{
    Red,
    Green,
    Blue,
};

class Red { };

int main()
{
    Red r;    // 컴파일 에러가 발생하지 않는다.
    EColor color = EColor::Red;    // 대신 접근지정자를 이용해 스코프를 지정해주어야 한다.

    return 0;
}

 

열거형을 표준 열거형이 아닌 enum class로 선언하면 열거형 내부에 존재하는 이름으로 클래스를 선언해도 문제가 발생하지 않는다. 하지만 표준 열거형에서는 Red라는 이름만으로 열거형 변수에 값을 넣어줄 수 있었지만 enum class 열거형에서는 열거형 변수에 값을 넣어주기 위해서는 반드시 접근지정자를 이용해서 스코프를 지정해주어야만 컴파일 에러 없이 값을 대입할 수 있게 된다. 이러한 형태의 이유 역시 MSDN에 설명되어 있는데 그것을 보면 왜 이런 형태를 가지는지 충분히 이해할 수 있게 된다.

 

enum class EColor
{
    Red,
    Green,
    Blue,
};

 

enum class로 선언된 EColor의 경우에는 다음과 같은 의미 체계를 가지게 된다.

 

class EColor
{
public:
    static const int Red = 0;
    static const int Green = 1;
    static const int Blue = 2;
};

 

위와 같은 형태를 가지게 된다는 것을 생각해보면 왜 enum class 열거형 변수에 스코프를 지정해 주어야하는지 명확하게 이해할 수 있다.

 

 

 

 

 

2. 표준 열거형은 타입 세이프(Type safe)하지 않다.

일반적으로 프로그래머들은 언어의 동작에서 프로그래머가 무의식적으로 실수를 유도하게 되는 동작을 좋아하지 않는다. 만약 프로그래머가 명시적으로 한 행동에서 문제가 발생하면 명백한 프로그래머의 실수이고 그 문제점을 찾아내기도 쉽지만, 프로그래머가 명시하지 않았는데 언어가 묵시적으로 처리해서 발생하는 문제는 찾아내기 어렵기 때문이다.

 

이러한 측면에서 표준 열거형이 발생시키는 문제가 몇 가지 있다.

 

첫 번째 문제로는 정수형의 변수에 곧바로 대입이 가능하다는 것이다(하지만 정수형에서 열거형으로의 대입은 명시적인 형 변환이 필요하다). 이것으로 인해서 발생하는 문제점에 대해서 경험이 많지 않아서 언급하지는 못하겠지만, 이런 묵시적인 동작으로 인해서 프로그래머가 실수를 할 가능성이 커지는 것은 부인할 수 없는 문제이다.

 

#include <iostream>
using namespace std;

enum EColor
{
    Red,
    Green,
    Blue,
};

int main()
{
    EColor color = Red;
    int i = color;    // 바로 대입이 가능하다.

    return 0;
}

 

하지만 열거형을 enum class로 선언할 경우, 다음과 같이 더 이상 명시적으로 형 변환을 거치지 않는 이상 열거형 변수나 값은 정수형에 대입할 수 없게 된다.

 

#include <iostream>
using namespace std;

enum class EColor
{
    Red,
    Green,
    Blue,
};

int main()
{
    EColor color = EColor::Red;
    int i = static_cast<int>(color);    // 명시적인 형 변환을 거쳐야만 대입이 가능하다.

    return 0;
}

 

두 번째 문제는 명시적인 형 변환 없이도 다른 열거형 간의 비교 연산이 가능하다는 것이다.

 

#include <iostream>
using namespace std;

enum EColor
{
    Red,
    Green,
    Blue,
};

enum EDirection
{
    Forward,
    Backward,
    Right,
    Left,
    Up,
    Down,
};

int main()
{
    EColor color = Red;
    EDirection dir = Up;
   
    if (color == dir)    // 문제없이 비교되고 컴파일 된다.
    {
        cout << "같다" << endl;
    }

    return 0;
}

 

이것이 무슨 문제가 되는지 이해하지 못할 수도 있겠지만, 위의 코드를 살펴보면 EColor 열거형과 EDirection 열거형의 종류와 내용, 그 의미는 명백하게 다르다. 이러한 상황을 프로그래머가 의도한 것이라면 모르겠지만, 이런 상황을 의도하지 않았고 실수로 전혀 다른 열거형을 비교하게 된 것이라면 문제가 될 것이고, 이러한 문제는 컴파일러가 프로그래머에게 알려주지 않기 때문에 찾아내기도 쉽지 않을 것이다.

 

하지만 열거형을 아래와 같이 enum class로 선언하면 명시적인 형 변환을 거치지 않는 이상 다른 열거형에 대한 비교 연산은 불가능해진다.

 

#include <iostream>
using namespace std;

enum class EColor
{
    Red,
    Green,
    Blue,
};

enum class EDirection
{
    Forward,
    Backward,
    Right,
    Left,
    Up,
    Down,
};

int main()
{
    EColor color = EColor::Red;
    EDirection dir = EDirection::Forward;
   
    if (color == (EColor)dir)    // 명시적으로 형 변환을 거친 후에야 비교 연산이 가능해진다.
    {
        cout << "같다" << endl;
    }

    return 0;
}

 

이러한 동작은 프로그래머의 무의식적인 실수로 인한 버그를 막아주는 것이고, 문제를 발생시킬 수 있는 동작의 경우에는 프로그래머가 의도적으로 코드를 작성해야 하고 그로인해 발생할 문제에 대한 대처와 책임을 충분히 해야한다는 것을 의미한다.

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

'C++' 카테고리의 다른 글

[C++ 11] static_assert  (0) 2017.05.23
[C++ 11] Auto Vectorization  (1) 2016.11.01
[C++ 11] Range-Based For  (0) 2016.11.01
[C++ 11] Scoped Lock  (0) 2016.11.01

static_assert

 

프로그래밍을 하는 과정에서 버그의 발생과 디버그는 필연적이다. 아무리 설계가 완벽하다고 해도, 코드의 작성자가 인간인 이상 실수로 인하여 버그는 발생하기 때문에 코딩 이후에는 반드시 테스트와 디버그가 이루어져야 한다.

 

게임 프로그래밍의 경우에는 예외처리(Exception Handling)을 성능 상의 문제로 잘 사용하지 않고 개발 도중에 버그를 잡기 위해서 assert를 사용하는 경우가 많다. assert는 <assert.h> 헤더를 포함시키면 사용할 수 있으며 어떤 식이 참인지 거짓인지 판별해주고 그 식이 거짓이라면 에러 메시지 박스를 띄우고 어느 cpp의 몇 번째 줄에서 중단되었는지 알려주고 프로그램이 종료된다. 이 기능은 디버그 빌드에서만 작동하고 릴리즈 빌드에서는 작동하지 않는다.

 

기존의 assert 사용 예시

#include <assert.h>

 

class Player

{

// 플레이어에 대해 정의된 클래스

// ...

}

 

class GameManager

{

// 게임을 관리하는 매니저

// ...

 

static Player* GetPlayer(/*특정조건*/)

{

// 특정 조건에 맞는 플레이어를 반환한다.

// 만약 조건에 맞는 플레이어가 없다면 nullptr을 반환

}

}

 

int main()

{

Player * player = GetPlayer();

// 만약 player가 nullptr이라면 프로그램은 정지되고 에러 메시지 박스를 출력될 것이다.

assert(player != nullptr);

}

 

C++ 11에 들어서 새로 도입된 static_assert라는 것이 있는데 이것은 별도의 헤더를 포함시키지 않고도 사용할 수 있다. 이 static_assert가 기존의 assert와 다른 점은 기존의 assert는 런타임 도중에만 작동해서 해당 코드가 실행되기 전에는 에러가 발생하는지 알기 어려운 반면에 static_assert는 컴파일 타임에 발생하기 때문에 문제가 발생할 부분이라면 해당 코드가 작동하지 않을 확률이 높다고 하더라도 반드시 에러를 잡아낼 수 있다는 것이다. 다만 컴파일 타임에만 작동하는 static_assert의 특성 상 컴파일 타임에 결정되지 않았고 런타임이 되지 않으면 알 수 없는 부분에는 사용할 수 없다. 예를 들자면 위의 assert 예시 코드에서처럼 player 객체가 nullptr인지는 런타임 동안 GetPlayer()함수를 지나봐야만 결정되기 때문에 컴파일 타임에는 알 수 없어서 저런 곳에는 static_assert를 사용할 수 없다.

 

 

static_assert 사용 예시

/* 기존에 사용되던 구조체 a

struct a

{

int i

}

//*/

 

//* 수정된 구조체 a

struct a

{

int i;

float f;

}

//*/

 

int main()

{

static_assert(sizeof(a) == 8, "Old struct a used.");

}

 

위의 예시처럼 구조체 a가 수정된 이후에 실수로 이전 구조체를 사용하고 있는지 컴파일 타임에 확인해서 발생할 버그를 미리 막을 수 있게 된다. 다음은 각 상황에서 static_assert의 반응이다 :

 

실수로 이전의 구조체를 사용한 경우 컴파일 에러를 발생시킨다.

 

구조체를 제대로 사용하면 컴파일 에러를 발생시키지 않는다.

 

static_assert를 사용할 때, 주의할 점은 유니코드와 한글을 지원하지 않기 때문에 에러 메시지를 작성할 때, 멀티바이트 영어로 작성하는게 좋다.

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

'C++' 카테고리의 다른 글

[C++11] enum class  (1) 2017.07.17
[C++ 11] Auto Vectorization  (1) 2016.11.01
[C++ 11] Range-Based For  (0) 2016.11.01
[C++ 11] Scoped Lock  (0) 2016.11.01

요즘 칩에는 벡터 레지스터라는 개념으로 메모리를 배치하는데, 이 벡터 레지스터 하나에 정수형이나 실수형 4개를 한꺼번에 담아둘 수 있다고 한다.

 

loop 작업시 float이나 int 형식의 4바이트 변수의 배열에 대해 하나씩 훑으며 작업할 때 굳이 하나씩 작업하지 않고 4개를 묶어서 16바이트를 벡터 형식으로 한 번에 처리할 수 있고 이것은 물론 스칼라 형식으로 하나씩 작업하는 것보다 빠르다(이론 상 최대 4배까지 빠를 수 있다).

 

이런 것을 Vectorization이라고 한다. 일반적으로 최신 컴파일러는 이런 Vectorization을 자동으로 수행해준다.

 

어떤 조건하에선 수행하지 못하는 경우가 있다. 수백만줄의 코드를 모두 체크하지는 못하기 때문에 이 체크에 실패하면 Auto Vectorization에 실패한다.

 

Auto Vectorization에 실패했을때 알아내는 방법은 프로그래머는 어셈블리어 코드를 확인하는 방법 밖에 없는데 이것은 어려운 일이다

그리고 Debug 빌드에서는 최종 코드를 내는 것이 아니기 때문에 최적화 기능의 상당부분이 꺼져있어서 Vectorization 또한 수행되지 않는다.

 

10000개 짜리 float 배열 두 개에 대한 loop 작업을 10000회를 수행했을 때의 결과물이다.

 

Release : x64

역으로 성능이 감소하는 케이스가 발생하기는 하지만 대부분 1%이하의 확률이다.

평균적으로 40~50%의 성능 향상을 기대할 수 있다.

 

Release : x86

x64 버전과 거의 비슷한 수준의 결과물을 얻을 수 있다.

반복적으로 실험한 결과 평균 성능 향상 비율을 2~10% 가량 떨어진다.

 

Debug

Debug 빌드에서는 최적화 기능이 꺼져있기 때문에 성능 향상이 거의 없음을 알 수 있다.

 

 

Vectorization 성능 비교 코드

#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;
#define ARRSIZE 100000
class Vectorization
{
private:
    int mWorkCount;
    __int64 mTotalVectorTime;
    __int64 mTotalNoVectorTime;
    __int64 mNowVectorTime;
    __int64 mNowNoVectorTime;
    float mMinImproveTime;
    float mMaxImproveTime;
    int mPerformDecreseCase;
    int mPerformIncreseCase;
    int mTotalWorkCount;
public:
    Vectorization() : mWorkCount(0), mTotalVectorTime(0), mTotalNoVectorTime(0),
        mNowVectorTime(0), mNowNoVectorTime(0), mMinImproveTime(0.f), mMaxImproveTime(0.f),
        mPerformDecreseCase(0), mPerformIncreseCase(0), mTotalWorkCount(0)
    {}
    void CalculateWorkTime(float* a, float* b, int arrsize, bool isVector)
    {
        __int64 t1;
        __int64 t2;
        for (int i = 0; i < arrsize; i++)
        {
            a[i] = b[i] = i;
        }
        QueryPerformanceCounter((LARGE_INTEGER*)&t1);
        if (isVector)
        {
            #pragma loop(vector)
            for (int i = 0; i < arrsize; i++)
            {
                a[i] += b[i];
            }
        }
        else
        {
            #pragma loop(no_vector)
            for (int i = 0; i < arrsize; i++)
            {
                a[i] += b[i];
            }
        }
        QueryPerformanceCounter((LARGE_INTEGER*)&t2);
        if (isVector)
        {
            mTotalVectorTime += mNowVectorTime = t2 - t1;
        }
        else
        {
            mTotalNoVectorTime += mNowNoVectorTime = t2 - t1;
        }
    }
    void CalcNowWorkImprovePerform()
    {
        float ImproveRate = 100.0f - (float)mNowVectorTime / (float)mNowNoVectorTime * 100.0f;
        if (ImproveRate < mMinImproveTime)
        {
            mMinImproveTime = ImproveRate;
        }
        else if (ImproveRate > mMaxImproveTime)
        {
            mMaxImproveTime = ImproveRate;
        }
        if (ImproveRate < 0)
        {
            mPerformDecreseCase++;
        }
        else
        {
            mPerformIncreseCase++;
        }
        cout << "현재 벡터화 작업시간 :: " << mNowVectorTime << endl;
        cout << "현재 비벡터화 작업시간 :: " << mNowNoVectorTime << endl;
        printf("현재 성능 향상 비율 :: %.2f %%\n\n", ImproveRate);
        mTotalWorkCount++;
    }
    void CalcTotalWorkImprovePerform()
    {
        float improveRate = 100.0f - (float)mTotalVectorTime / (float)mTotalNoVectorTime * 100.0f;
        cout << "총 벡터화 작업시간 :: " << mTotalVectorTime << endl;
        cout << "총 비벡터화 작업시간 :: " << mTotalNoVectorTime << endl << endl;;
        printf("최저 성능 향상 비율 :: %.2f %%\n", mMinImproveTime);
        printf("최고 성능 향상 비율 :: %.2f %%\n", mMaxImproveTime);
        printf("평균 성능 향상 비율 :: %.2f %%\n\n", improveRate);
        cout << "성능 감소 케이스 :: " << mPerformDecreseCase << "회" << endl;
        printf("성능 감소 케이스 비율 :: %.2f %%\n\n", (float)mPerformDecreseCase / (float)mTotalWorkCount * 100.f);
        cout << "성능 향상 케이스 :: " << mPerformIncreseCase << "회" << endl;
        printf("성능 향상 케이스 비율 :: %.2f %%\n\n", (float)mPerformIncreseCase / (float)mTotalWorkCount * 100.f);
        cout << "총 성능 비교 횟수 :: " << mTotalWorkCount << "회" << endl;
    }
};
 
int main()
{
    Vectorization v;
    float fArr1[ARRSIZE];
    float fArr2[ARRSIZE];
    for (int i = 0; i < 10000; i++)
    {
        v.CalculateWorkTime(fArr1, fArr2, ARRSIZE, true);
        v.CalculateWorkTime(fArr1, fArr2, ARRSIZE, false);
        v.CalcNowWorkImprovePerform();
    }
    v.CalcTotalWorkImprovePerform();
}

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

'C++' 카테고리의 다른 글

[C++11] enum class  (1) 2017.07.17
[C++ 11] static_assert  (0) 2017.05.23
[C++ 11] Range-Based For  (0) 2016.11.01
[C++ 11] Scoped Lock  (0) 2016.11.01

C++ 11부터 지원되는 Range-based for(범위 기반 for)는 기존의 C++ STL의 컨테이너, 배열 등에서 반복문을 좀 더 쉽게 사용하게 해주는 기능이다.

 

이 기능은 Microsoft의 Visual C++ 기능인 for each문과 그 기능과 사용법이 비슷해서 쉽게 사용할 수 있다.

 

기존의 for문과 iterator를 이용한 방법과 Visual C++ for each문을 이용한 방법 그리고 C++ 11 표준의 Range-based for문을 이용한 방법을 비교하자면 아래와 같다.

 

vector<Object*> vect;

// 기존의 for문과 auto 키워드를 이용한 iterator 반복 처리
for (auto obj = vect.begin(); obj != vect.end(); obj++)
{
    // Object에 처리
}

// Visual C++ 타입의 for each문을 이용한 반복 처리
for each(auto obj in vect)
{
    // Object에 처리
}
// C++ 11타입의 Range-based for문을 이용한 반복 처리
for (auto obj : vect)
{
    // Object에 처리
}

 

코드를 살펴보면 분명하게 미세하게나마 코드의 양이 줄어드는 것이 보인다. 같은 동작을 하는 데도 기존의 for문과 auto 형식의 복잡한 타이핑 없이 처리할 수 있다는 것은 프로그래머의 피로도를 상당히 줄여준다.(물론 auto 키워드가 나오기 전에는 더더욱 복잡했었다.)

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

'C++' 카테고리의 다른 글

[C++11] enum class  (1) 2017.07.17
[C++ 11] static_assert  (0) 2017.05.23
[C++ 11] Auto Vectorization  (1) 2016.11.01
[C++ 11] Scoped Lock  (0) 2016.11.01

Multi thread 프로그래밍을 할때 현재 thread가 작업하는 메모리를 다른 thread에 덮어쓰거나 잘못 사용하는 일이 발생하지 않도록 주의해야한다.

이러한 문제를 대비하기 위한 기법이 바로 thread 동기화인데 Critical Section, Semaphore, Mutex 등이 있다.

이러한 것들을 사용하지 않는다면

 

Tread 동기화를 사용하지 않은 스레드 작업

#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
int iArr[500];      // 두 thread가 작업할 메모리 공간

void test1()    // test1 함수는 iArr에 0을 채운다.
{
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 0;
        Sleep(1);
    }
}

void test2()    // test2 함수는 iArr에 1을 채운다.
{
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 1;
        Sleep(1);
    }
}

int main()
{
    // 각 함수를 스레드로 작동
    thread t1(test1);
    thread t2(test2);

    // 두 함수가 끝나기를 기다린다.
    t1.join();
    t2.join();

    // 결과값 출력
    for (int i = 0; i < 500; i++)
    {
        cout << iArr[i];
        if ((i +1) % 50 == 0)
        {
            cout << endl;
        }
    }
}

 

 

위의 결과 처럼 중간 중간 값을 덮어씌워서 오작동을 일으킬 수 있다.

 

하지만 위에서 말한 thread 동기화 기법을 사용하면 이 문제는 해결된다.

 

 

 

Mutex를 사용한 thread 작업

#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;

int iArr[500];
mutex m;    // Thread Lock을 걸 Mutex 클래스

void test1()
{
    m.lock();   // 메모리에 lock을 걸어 다른 thread에서 사용하지 못하게 한다.
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 0;
        Sleep(1);
    }
    m.unlock(); // 메모리에 대한 작업이 끝난 이후에 lock을 해제한다.
}

void test2()
{
    m.lock();
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 1;
        Sleep(1);
    }
    m.unlock();
}

int main()
{
    // 각 함수를 스레드로 작동
    thread t1(test1);
    thread t2(test2);

    // 두 함수가 끝나기를 기다린다.
    t1.join();
    t2.join();

    // 결과값 출력
    for (int i = 0; i < 500; i++)
    {
        cout << iArr[i];
        if ((i +1) % 50 == 0)
        {
            cout << endl;
        }
    }
}

 

 

Mutex를 사용하면 처음 lock을 건 thread에서 작업이 끝난 이후에야 다른 thread에서 그 메모리에 접근해서 작업이 가능하기 때문에 모든 배열에 1이 출력이 된다.

 

 

 

 

이렇게 별 문제가 없어 보이는 thread lock에는 문제가 있는데 바로 Race Condition이다. 이 race condition은 일반적으로 1번 thread와 2번 thread가 있고 데이터가 담긴 메모리 A, B가 있을 때,  1번 thread가 A메모리에 lock을 건 상태에서 B메모리에 작업을 하려고 하고, 2번 thread는 B메모리에 lock을 건 상태에서 A메모리에 작업을 하려고 할때 발생한다. 이렇게 되면 1번 thread와 2번 thread는 서로 자신의 작업이 끝나야 각각의 메모리의 lock 해제하는데 서로에게 lock이 걸린 메모리 때문에 작업을 그 이후로 진행할 수 없기 때문에 프로그램은 작동을 정지하고 만다. 이런 상황을 다른 말로 dead lock, 교착상태라고도 한다.

 

하지만 scoped lock은 사실 이런 완벽한 교착 상태를 예방하기 위한 것이라기 보다는 사소한 프로그래머의 실수를 방지하기 위한 것이다. 다음의 코드를 보자.

 

프로그래머의 실수!

#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
int iArr[500];
mutex m;
void test1()
{

    // 이 프로그래머는 훌륭하게 lock을 걸었지만
    m.lock();
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 0;
        Sleep(1);
    }
    // unlock을 까먹고 하지 않았다!
}

void test2()
{
    m.lock();
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 1;
        Sleep(1);
    }
    m.unlock();
}

int main()
{
    thread t1(test1);
    thread t2(test2);
    t1.join();
    t2.join();
    for (int i = 0; i < 500; i++)
    {
        cout << iArr[i];
        if ((i +1) % 50 == 0)
        {
            cout << endl;
        }
    }
}

 

위의 코드처럼 실수로 thread lock을 건 이후에 작업이 끝나고 unlock을 까먹는다면 2번 thread는 1번 thread가 걸어둔 lock이 해제되기를 영원히 기다릴 것이다. 위 코드처럼 간단한 thread 예제라면 이러한 문제를 쉽게 찾아내겠지만 수만줄을 넘어가고 여러 종류의 thread가 여러 개 돌아가는 커다란 프로그램에서 저런 기억하기 어려운 사소한 실수를 범하게 된다면 얼마나 긴 시간을 허비하게 될까?

 

 

 

 

그래서 나온 것이 scoped lock이라는 것인데 이것의 원리는 매우 간단하다. 우리가 사용하는 Mutex를 하나의 클래스로 가볍게 감싸는 것이다. 그리고 클래스의 생성자가 실행될 때 lock()을 실행하고 클래스의 소멸자가 실행될 때 unlock()을 실행해 주는 것이다.

이것은 프로그래머가 직접 구현해도 될 정도로 간단한 작업이지만 표준 C++에서는 이미 지원되고 있으니 해당 기능을 사용하면 된다.

 

Scoped Lock 사용법

#include <iostream>
#include <Windows.h>
#include <thread>
#include <memory> // Scoped lock 기능을 사용하기 위해 include 해야하는 header
#include <mutex>
using namespace std;
int iArr[500];
mutex m;
void test1()
{
    lock_guard<mutex> g(m);      
    // lock_guard 클래스의 템플릿에 mutex 클래스를 넣고 생성자에 우리가 사용하는 mutex 객체를 넣어주면 된다.
    // lock_guard 객체가 생성될 때 자동으로 thread에 lock이 걸린다.
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 0;
        Sleep(1);
    }
    // 그리고 함수가 끝날 때 or 스코프를 벗어날 때 자동으로 lock_guard 객체가 소멸되면서 thead lock이 해제된다.
}
void test2()
{
    lock_guard<mutex> g(m);
    for (int i = 0; i < 500; i++)
    {
        iArr[i] = 1;
        Sleep(1);
    }
}
int main()
{
    thread t1(test1);
    thread t2(test2);
    t1.join();
    t2.join();
    for (int i = 0; i < 500; i++)
    {
        cout << iArr[i];
        if ((i +1) % 50 == 0)
        {
            cout << endl;
        }
    }
}

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

'C++' 카테고리의 다른 글

[C++11] enum class  (1) 2017.07.17
[C++ 11] static_assert  (0) 2017.05.23
[C++ 11] Auto Vectorization  (1) 2016.11.01
[C++ 11] Range-Based For  (0) 2016.11.01

+ Recent posts