반응형

#include 를통해 오래 걸리던 시간을 줄일 수 있는 대안이 된다

다른 랭귀지 C#, Java 등에서 사용 되었던 기법들인데 이제 c++ 에 들어왔다

사실 들어온진 좀 됐는데 실험 기능이였고 계속 발전 되었던 형태이긴 하다

 

 


 

모듈(module) 이란?

C++20 이전의 C++에서는 필요한 함수 또는 클래스를 불러오기 위해 #include 전처리문을 이용해 왔다. 이런 헤더 파일 방식의 문제는..많지만 그 중에 필자가 가장 크리티컬하게 생각하는 부분은 #include 전처리문을 가리키고 있던 파일의 내용 그대로 치환해버려 헤더에 있는 필요하든 필요하지 않든 상관 없이 정의 되어있는 모든 기능을 포함하게 된다는 것이다. 예를 들어 cmath 헤더 파일에서 정작 내가 필요한 기능은 acos함수 하나 뿐이지만, acos를 사용하기 위해서 나는 헤더에 정의된 모든 함수들을 인클루드하고 컴파일 해야만 한다.

이미 현재 다른 언어들에서는 필요한 기능만을 가져 올 수 있는 기능을 제공하고 있지만 C++은 이번 C++20 스펙의 module을 통해 필요한 것만 가져올 수 있는 기능을 제공한다.

기존 C++시스템과 module 도입 이후의 차이와 이점에 대해서 [여기]에 정리 되어 있으니 살펴 보도록 하자.

모듈 코드 작성하기

앞에서 알아 본바와 같이 모듈의 의도와 개념은 간단 명료하다. 이제 모듈을 사용하기 위해 필요한 것들을 알아 보자. 모듈을 사용하기 위해 우리가 알아야 할 것도 간단 명료하다.

module, import, export 이 세가지 키워드를 기억하자
  • module : 모듈의 이름을 지정
    eg) module Math : 모듈의 이름은 'Math'이다.
  • import : 가져올 모듈의 이름을 지정
    eg) import Math : 가져올 대상 모듈의 이름은 'Math'이다.
  • export : 모듈에서 내보낼 기능(함수)의 인터페이스를 지정
    eg) export int sum(int, int) : 내보낼 함수의 이름은 sum이고 리턴 타입은 int, 인자는 int, int다.

모듈 선언(declaration)

기존 #include 전처리 방식은 컴파일러와 상관 없이 선언은 헤더 파일에 위치하고 구현은 .cpp 파일 적성하는 방식이었다면 module은 각 컴파일러마다 각각 다른 방식으로 작성된다.

cl.exe(Microsoft 비주얼 스튜디오)

cl.exe는 비주얼 스튜디오의 컴파일러 이름이다. cl.exe는 모듈을 선언하기 위해 확장자가 ixx인 모듈 인터페이스 파일을 사용한다. 모듈을 만들기 위해 가장 먼저 할 일은 프로젝트에서 아래와 같이 'C++ 모듈 인터페이스 단위(.ixx)'를 선택하여 파일을 생성한다.

확장자가 .ixx인 모듈 인터페이스 파일을 만들고 내보낼 모듈의 이름과 인터페이스를 작성한다.

 
// ModuleA.ixx 모듈 인터페이스 파일

export module ModuleA;      // 내보낼 모듈의 이름 지정

namespace Foo
{
​​​​export int MyIntFunc()  // 모듈에서 내보낼 기능(함수)의 인터페이스를 지정
​​​​{
​​​​​​​​return 0;
​​​​}
​​​​
​​​​export double MyDoubleFunc()
​​​​{
​​​​​​​​return 0.0;
​​​​}
​​​​
​​​​void InternalMyFunc()   // 모듈 내부에서만 사용하는 함수
​​​​{
​​​​}
}
  • 3라인의 'export module ModuleA'는 우리가 함수나 변수를 선언하는 것과 같이 모듈을 만들겠다는 선언이다.
  • 7라인과 12라인의 export가 선언되어 있는 MyIntFunc()와 MyDoubleFunc()는 다른 파일에서 이 모듈을 임포트(import)했을 때 사용할 수 있는 함수라는 것을 의미한다.
  • 17라인의 InternalMyFunc() 함수는 모듈 내부에서만 사용되고 이 모듀을 임포트하는 곳에서는 사용하지 못한다.
  • 위 예제와 같이 선언과 구현을 ixx파일에 모두 작성할 수도 있고 다음 예제와 같이 기존 .h, .cpp 구조 처럼 나눠서 작성할 수도 있다.
 
// 선언만 있는 ModuleA.ixx 모듈 인터페이스 파일

export module ModuleA;      // 내보낼 모듈의 이름 지정

namespace Foo
{
​​​​export int MyIntFunc();
​​​​
​​​​export double MyDoubleFunc();
​​​​
​​​​void InternalMyFunc();
}
 
// ModuleA.cpp 모듈 구현 파일

module ModuleA;         // 시작 부분에 모듈 선언을 배치하여 파일 내용이 명명된 모듈(ModuleA)에 속하도록 지정

namespace Foo
{
​​​​int MyIntFunc()     // 구현에서는 export 키워드가 빠진다.
​​​​{
​​​​​​​​return 0;
​​​​}
​​​​
​​​​double MyDoubleFunc()
​​​​{
​​​​​​​​return 0.0;
​​​​}
​​​​
​​​​void InternalMyFunc()
​​​​{
​​​​}
}
Visual Studio 2019 버전 16.2에서는 모듈이 컴파일러에서 완전히 구현 되지 않았기 때문에 모듈을 사용하기 위해서는 /experimental:module 스위치를 활성화 해야 한다. 하지만 이 글을 적는 시점에서 최신버전 컴파일러는 모든 스펙을 다 구현하였으므로 모듈을 사용기 위해서는 최신 버전으로 업데이트할 필요가 있다.

module, import 및 export 선언은 C++20에서 사용할 수 있으며 C++20 컴파일러를 사용한다는 스위치 활성화가 필요하다(/std:c++latest ). 보다 자세한 사항은 [C++20] 컴파일 항목을 참고 하도록 하자.

참고로 필자의 경우 x86 32-bit 프로젝트를 생성해서 모듈을 사용하려고 했을 때 제대로 컴파일 되지 않았다. 비주얼 스튜디오에서 모듈을 사용하기 위해서는 64-bit 모드로 프로젝트를 설정해야 한다.

gcc

gcc의 경우 모듈을 선언하기 위해 .cpp 파일을 사용하지만 컴파일 시 -fmodules-ts 옵션을 이용해 컴파일 해야 한다. gcc에서 모듈을 사용하기 위해서는 gcc 버전 11이상이 필요하다. gcc 11의 설치 방법에 대해서는 [여기]를 참고 하도록 한다. gcc 컴파일에 대한 자세한 방법은 [여기]를 참고하자.

Global Module Fragment

적절하게 번역할 단어를 찾지 못해서 그대로 글로벌 모듈 프래그먼트(Global Module Fragment)라고 부르도록 하겠다. 글로벌 모듈 프래그먼트는 #include와 같은 전처리 지시자를 적어 주는 곳이라 생각하면 된다. 글로벌 모듈 프래그먼트는 익스포트(export)되지 않는다. 여기에 #include를 이용해 헤더를 불러왔다고 하더라도 모듈을 가져다 쓰는 곳에서 include의 내용를 볼 수 없다는 뜻이다.

 
// math1.ixx
module;                   // global module fragment (1)

#include <numeric>        // #include는 여기 들어간다
#include <vector>

export module math;       // module declaration (2)

export int add(int fir, int sec){
​​​​return fir + sec;
}

export int getProduct(const std::vector<int>& vec) {
​​​​return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
}

모듈 불러오기(import)

모듈을 불러와 사용하는 방법은 기존의 include와 매우 비슷하다. #include 대신 import 키워드를 사용한다

 
import ModuleA; // 모듈 임포트

#include <iostream>

int main()
{
​​​​std::cout << Foo::MyIntFunc() << std::endl;
​​​​return 0;
}

마치며

필자의 경우 비주얼 스튜디오에서는 #include, import 순서에 상관 없이 정상적으로 컴파일 되었지만, gcc에선 헤더와 모듈의 순서에 따라 컴파일 에러가 발생했었다(내가 뭔가를 잘못해서 그럴 수도 있지만..). 모듈을 임포트 후 shared_ptr을 위해 memory 헤더를 인클루드하면 컴파일 오류가 발생하고, 헤더를 먼저 인클루드 후 모듈을 임포트하면 정상적으로 컴파일 되었다. 

 
// compile : g++ -std=c++20 -fmodules-ts hello.cc main.cc

// import hello;      // Compile Error!!
// #include <memory>

#include <memory>     // Compile Success
import hello;

int main()
{
​​​​std::shared_ptr<int> i = std::shared_ptr<int>(new int);
​​​​greeter("world");
​​​​return 0;
}

위 예제의 전체 프로젝트 코드는 [여기]에서 확인 할 수 있다.

 

 

ref : https://kukuta.tistory.com/389

반응형
반응형

According to the following test, it seems that a std::vector<int> increases its capacity in this way:

  • it happens when we push_back() and the capacity is already full (i.e. v.size() == v.capacity()), it has to be noted that it doesn't happen a little bit before
  • the capacity increases to 1.5 times the previous capacity

Question: why this 1.5 factor? Is it implementation-dependent? Is it optimal?

Also, is there a way to analyze, in this code, when exactly a reallocation happens? (sometimes maybe the capacity can be increased without moving the first part of the array)

 

Note: I'm using VC++ 2013

 

 

 

I think an important aspect to the answer of why the factor is 1.5 is preferred over 2.0, is that the standard c++ allocator model does not support a realloc function:

Realloc- in Standard allocator

If it would exist, and it would be used in std::vector, the mentioned memory fragmentation problem would be minimized in all cases where the factor 1.5 is better compared to e.g. factor 2.0 capacity increase.

The factor of 1.5 (or more precisely the golden ratio) is just optimum when the buffer is always relocated on every buffer size increase (as the other answers explain).

With realloc and if there is space after the current memory block, it would just be enlarged without copying. If there is no space, realloc call would of course move the datablock. However then the free'd blocks (if this happens multiple times) would not be contiguous anyway. Then it doesnt matter whether 1.5 or 2 is the factor for buffer enlargement... Memory is fragmented anyways.

So a realloc approach in std::vector would help wherever the factor 1.5 has the advantage over 2.0 and would save the data copy.

 

 

 

ref : https://stackoverflow.com/questions/45403052/when-does-a-stdvector-enlarge-itself-when-we-push-back-elements

반응형
반응형

 

template<typename T>
struct Length;

template<>
struct Length<TypeList<>>
{
	enum { value = 0 };
};

template<typename T, typename... U>
struct Length<TypeList<T, U...>>
{
	enum { value = 1 + Length<TypeList<U...>>::value };
};

using TL = TypeList<player, mage,="" knight,="" archer="">;</player,>

이건 TL 들의 사이즈를 구할 수 있는 템플릿예시입니다

 

 

 

 

다음과 같은 상속 구조가 있을 경우

class Player
{
public:

	virtual ~Player() { }


};

class Knight : public Player
{
public:
	Knight() {  }
};

class Mage : public Player
{

public:
	Mage() {  }
};

class Archer : public Player
{

public:
	Archer() {  }
};

class Dog
{
};

 

 

template<typename From, typename To>
class Conversion
{
private:
	using Small = __int8;
	using Big = __int32;

	static Small Test(const To&) { return 0; }
	static Big Test(...) { return 0; }
	static From MakeFrom() { return 0; }

public:
	enum
	{
		exists = sizeof(Test(MakeFrom())) == sizeof(Small)
	};
};

 

Conversion은 아래 처럼 사용 할수 있는데 

 

bool canConvert1 = Conversion<Player, Knight>::exists;   0
bool canConvert2 = Conversion<Knight, Player>::exists;   1
bool canConvert3 = Conversion<Knight, Dog>::exists;       0

 

이렇게 호출 할 경우 

아래 두 함수 중에서 

 

static Small Test(const To&) { return 0; }


static Big Test(...) { return 0; }

 

 

Test 함수가 호출 될때 From 타입을 To 타입으로 캐스팅 할수 있다면 즉

 

모든 타입을 다 받는 Test(...)  가 아닌 특정 지정한 To 타입으로 

Test 함수가 불릴 수 있다면 상속관계이 있는 것임으로

 

이를 이용해서

 

exists = sizeof(Test(MakeFrom())) == sizeof(Small)

 

캐스팅이 가능한지 불가능한 관계인지를 컴파일 타임때 미리 계산 할 수 있다

 

 

 

 

 

 

아래는  타입에 대한 인덱스를 구할수 있는 코드

template<typename TL, typename T>
struct IndexOf;

template<typename... Tail, typename T>
struct IndexOf<TypeList<T, Tail...>, T>
{
	enum { value = 0 };
};

template<typename T>
struct IndexOf<TypeList<>, T>
{
	enum { value = -1 };
};

template<typename Head, typename... Tail, typename T>
struct IndexOf<TypeList<Head, Tail...>, T>
{
private:
	enum { temp = IndexOf<TypeList<Tail...>, T>::value };

public:
	enum { value = (temp == -1) ? -1 : temp + 1 };
};

 

 

define 좋진 않은데 그냥 예시라 보면 될듯합니다

 

[플레이어 코드 TL 예시]

using TL = TypeList<class Player, class Mage, class Knight, class Archer>;

#define DECLARE_TL	using TL = TL; int32 _typeId;
#define INIT_TL(Type)	_typeId = IndexOf<TL, Type>::value;



class Player
{
public:
	Player()
	{
		INIT_TL(Player);
	}
	virtual ~Player() { }

	DECLARE_TL
};

class Knight : public Player
{
public:
	Knight() { INIT_TL(Knight); }
};

class Mage : public Player
{

public:
	Mage() { INIT_TL(Mage); }
};

class Archer : public Player
{

public:
	Archer() { INIT_TL(Archer) }
};

class Dog
{

};

 

컴파일타임때 해당 타입이 몇번의 인덱스인지 구할수 있게 됩니다

tempalte 가변인자 특징을 활용

 

 

 

template<typename TL, int32 index>
struct TypeAt;

template<typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0>
{
	using Result = Head;
};

template<typename Head, typename... Tail, int32 index>
struct TypeAt<TypeList<Head, Tail...>, index>
{
	using Result = typename TypeAt<TypeList<Tail...>, index - 1>::Result;
};

이것은 컴파일타임에 타입들중에서 지정한 인덱스에 해당하는 타입을 알수 있는 코드입니다

 

 

 

 

 

 

타입 변환을 위한 클래스들간의 타입 변환이 일어날수 있는가 2중 for 문돌리듯이 템플릿을 만들어 미리 그 결과를 저장해 놓을수 있는데 

 

 

 

template<int32 v>
struct Int2Type
{
	enum { value = v };
};

template<typename TL>
class TypeConversion
{
public:
	enum
	{
		length = Length<TL>::value
	};

	TypeConversion()
	{
		MakeTable(Int2Type<0>(), Int2Type<0>());
	}

	template<int32 i, int32 j>
	static void MakeTable(Int2Type<i>, Int2Type<j>)
	{
		using FromType = typename TypeAt<TL, i>::Result;
		using ToType = typename TypeAt<TL, j>::Result;

		if (Conversion<const FromType*, const ToType*>::exists)
			s_convert[i][j] = true;
		else
			s_convert[i][j] = false;

		MakeTable(Int2Type<i>(), Int2Type<j + 1>());
	}

	template<int32 i>
	static void MakeTable(Int2Type<i>, Int2Type<length>)
	{
		MakeTable(Int2Type<i + 1>(), Int2Type<0>());
	}

	template<int j>
	static void MakeTable(Int2Type<length>, Int2Type<j>)
	{
	}

	static inline bool CanConvert(int32 from, int32 to)
	{
		static TypeConversion conversion;
		return s_convert[from][to];
	}

public:
	static bool s_convert[length][length];
};

template<typename TL>
bool TypeConversion<TL>::s_convert[length][length];

 

이런 식으로 작성 하면 됩니다

 

s_convert 이곳에는 컴파일 타임때 타입간의 변환이 가능한지를 미리 계산해 놓은 곳이 되며

 

using TL = TypeList<class Player, class Mage, class Knight, class Archer>;

 

 

static inline bool CanConvert(int32 from, int32 to)
{
static TypeConversion conversion;
return s_convert[from][to];
}

 

이 함수로 타입간의 결과를 리턴 받을수 있습니다 : 이건 런타임이겠지요

 

 

 

 

이제 [플레이어 코드 TL 예시] 를 기준으로

 

 

아래 처럼 작성 할수 있는데

 

타입 캐스팅이 가능하다면 static_cast 로 캐스팅 해서 반환, 그렇지 않으면 nullptr 반환(dynamic_cast 와 유사)을 하는 것입니다

 

 


//타입 캐스팅이 가능하다면 static_cast 로 캐스팅 해서 반환, 그렇지 않으면 nullptr 반환(dynamic_cast 와 유사)
template<typename To, typename From>
To TypeCast(From* ptr)
{
	if (ptr == nullptr)
		return nullptr;

	using TL = typename From::TL;

	if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value))
		return static_cast<To>(ptr);

	return nullptr;
}


template<typename To, typename From>
shared_ptr<To> TypeCast(shared_ptr<From> ptr)
{
	if (ptr == nullptr)
		return nullptr;

	using TL = typename From::TL;

	if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value))
		return static_pointer_cast<To>(ptr);

	return nullptr;
}

//이건 캐스팅이 가능하냐 그렇지 않냐 Is, As 캐스팅 같은 것..
template<typename To, typename From>
bool CanCast(From* ptr)
{
	if (ptr == nullptr)
		return false;

	using TL = typename From::TL;
	return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}


template<typename To, typename From>
bool CanCast(shared_ptr<From> ptr)
{
	if (ptr == nullptr)
		return false;

	using TL = typename From::TL;
	return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}

 

remove_pointer_t 는 들어온 타입의 포인터를 제거 하는 것

 

 

 

예시 Player를 Knight 로 캐스팅

 

dynamic_cast 처럼 null 이 나오는것을 알수 있습니다

 

 

예시 본체(인스턴스)가 Knight 였고 이것을 player에 담은 후 player를 Knight 로 캐스팅하면 성공

 

Knight 가 초기화 되면서 player index 가 Knight 의 index로 바뀐 상태가 되고 

멤버변수 index에 Knight의 index 가 들어가게 됨

런타입에서 Knight 에 맞는 index를 찾기 때문에가능

 

 

반응형
반응형
This code does not compile, but i couldnot find what is wrong with the code. I think shared_ptr matters.

#include <memory>
#include <iostream>
using namespace std;

class A {
public:
  virtual const void test() const = 0;
  ~A() {  }
};
class AImpl : public A {
public:
  const void test() const {
    std::cout << "AImpl.test" << std::endl;
  }
};
class B {
public:
  B() {  }
  ~B() { 
  }

  shared_ptr<A> CreateA() {
    a_ = make_shared<AImpl>(new AImpl());
    return a_;
  }
private:
  shared_ptr<A> a_;
};

int main() {

  B *b = new B();
  shared_ptr<A> p = b->CreateA();
  if (b) {
    delete b;
    b = NULL;
  }
}

 

 

ou are using make_shared incorrectly. You dont need to use new in make_shared, it defeats the whole purpose of this function template.

This function is typically used to replace the construction std::shared_ptr(new T(args...)) of a shared pointer from the raw pointer returned by a call to new. In contrast to that expression, std::make_shared typically allocates memory for the T object and for the std::shared_ptr's control block with a single memory allocation (this is a non-binding requirement in the Standard), where std::shared_ptr(new T(args...)) performs at least two memory allocations.

a_ = make_shared<AImpl>(); // correct
//a_ = make_shared<AImpl>(new AImpl()); // not correct

 

 

ref : https://stackoverflow.com/questions/39651748/whats-wrong-with-the-code-using-shared-ptr-with-pure-virtual-base-class

반응형
반응형

Intro


std에 존재하는 lock 방법들은 운영체제 단에서 지원하는 크리티컬 섹션을 래핑한 mutex를 기반으로 개발자가 쉽게 락을 걸 수 있도록 도와줍니다. 앞서 배운 lock_guard 또한 mutex를 쉽게 사용할 수 있도록 도와 줍니다.

여기서 중요한 점은 mutex와 lock은 분명히 다른 역할을 하는 것입니다.
mutex는 다른 쓰레드가 공유자원에 접근을 하지 못하게 만드는 동기화 객체입니다. 공유 자원에 접근하기 위해서는 mutex에 대한 잠금을 획득하고, 접근 후에는 잠금을 해제 해야 합니다.

lock은 이러한 mutex를 기반으로 잠글수 있는 기능을 캡슐화 한 객체 입니다. 쉽게 생각하면 자물쇠가 lock 이고, 자물쇠의 열쇠 구멍을 mutex라고 생각 할 수 있습니다. 이러한 객체들은 개발자가 쉽고 간편하게 공유자원의 동시접근을 막을 수 있도록 도와줍니다.

 

 

기본적인 lock의 종류


std::mutex

앞서 정리했던 mutex 역시 가장 기본적이고 핵심이 되는 부분입니다. c++의 lock은 이러한 mutex를 베이스로 개발자가 더 쉽고 간편하게 다양한 기능들을 쓸 수 있도록 도와줍니다.

std::lock

C++11에서 추가된 std::lock은 기본적인 lock 클래스입니다.

#include <iostream>
#include <mutex>

std::mutex m1, m2;
int main() {
   std::thread th([&]() {
   std::lock(m1, m2);
   std::cout << "th1" << std::endl;
   m1.unlock();
   m2.unlock();
   });
   std::thread th2([&]() {
   std::lock(m1, m2);
   std::cout << "th2" << std::endl;
   m1.unlock();
   m2.unlock();
   });

   std::cout << "hello" << std::endl;
   th.join();
   th2.join();
}
C++

위의 코드처럼 std::lock은 여러개의 mutex를 한번에 잠글 수 있도록 도와 줍니다.

 


std::lock_guard

std::lock_guard는 많이 쓰이는 락 종류로써 다음처럼 객체 생성 시에 lock되며 객체가 소멸시에 unlock 되는 특성을 가지고 있습니다.

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m1;
int main() {
   std::thread th([&]() {
   std::lock_guard<std::mutex> lock_guard(m1);
   for (int i = 0; i < 100; i++) {
   std::cout << "th1" << std::endl;
   }
   });
   std::thread th2([&]() {
   std::lock_guard<std::mutex> lock_guard(m1);
   for (int i = 0; i < 100; i++) {
   std::cout << "th2" << std::endl;
   }
   });

   std::cout << "hello" << std::endl;
   th.join();
   th2.join();
}
C++
...
mutex.lock()

if(n == 10)
   return false;

mutex.unlock()
...
C++

또한 위와 같이 중간에 리턴되어 unlock이 되지 않는 문제를 해결 할 수 있습니다.

 


std::unique_lock

std::unique_lock은 기본적으로 lock_guard와 비슷한 특징을 가지고 있습니다. 둘 다 자신의 생명주기를 가지며 소멸 될 때 unlock 됩니다.
std::unique_lock은 기본적으로 생성과 동시에 lock이 걸리고 소멸시에 unlock되지만 그밖에도 옵션을 통해 생성시 lock을 안 시키기고 따로 특정 시점에 lock을 걸 수도 있습니다.

생성자의 인자로 mutex만 넘겨 준다면 생성 시에 lock이 걸리게 됩니다. 생성자의 인자로 mutex와 함께 std::defer_lock, std::try_to_lock, std::adopt_lock을 넘겨 줄 수 있습니다.
세가지 모두 컴파일 타임 상수 입니다.

  • std::defer_lock : 기본적으로 lock이 걸리지 않으며 잠금 구조만 생성됩니다. lock() 함수를 호출 될 때 잠금이 됩니다. 둘 이상의 뮤텍스를 사용하는 상황에서 데드락이 발생 할 수 있습니다.(std::lock을 사용한다면 해결 가능합니다.)
  • std::try_to_lock : 기본적으로 lock이 걸리지 않으며 잠금 구조만 생성됩니다. 내부적으로 try_lock()을 호출해 소유권을 가져오며 실패하더라도 바로 false를 반환 합니다. lock.owns_lock() 등의 코드로 자신이 락을 걸 수 있는 지 확인이 필요합니다.
  • std::adopt_lock : 기본적으로 lock이 걸리지 않으며 잠금 구조만 생성됩니다. 현재 호출 된 쓰레드가 뮤텍스의 소유권을 가지고 있다고 가정합니다. 즉, 사용하려는 mutex 객체가 이미 lock 되어 있는 상태여야 합니다.(이미 lock 된 후 unlock을 하지않더라도 unique_lock 으로 생성 해 unlock해줄수 있습니다.)
#include <mutex>
#include <thread>
#include <chrono>

struct Box {
   explicit Box(int num) : num_things{num} {}

   int num_things;
   std::mutex m;
};

void transfer(Box &from, Box &to, int num) {
   std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
   std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);

   // 2개의 뮤텍스를 동시에 lock
   std::lock(lock1, lock2);

   from.num_things -= num;
   to.num_things += num;
}

int main() {
   Box acc1(100);
   Box acc2(50);

   std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
   std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);

   t1.join();
   t2.join();
}

 

C++

위의 예제는 unique_lock의 사용 예제 입니다. 각 transfer 함수를 쓰레드로 실행하며 그때 인자로 mutex를 가진 Box 객체를 넘겨줍니다.
각 쓰레드에서는 lock을 걸고 계좌의 돈을 옮기는 로직을 수행합니다.

하지만 이와 같은 상황에서는 주석 친 부분과 같이 unique_lock을 2줄에 걸쳐 잠그는 코드는 사용해서는 안됩니다. 이러한 코드는 문제를 발생 시 킬 수 있는 여지가 존재합니다.
만약에 2줄에 걸쳐 unique_lock을 이용해 락을 건다면 이때는 양쪽 다 락이 걸리는 데드락이 발생할 수 있기 때문입니다. 여기서는 std::defer_lock을 이용해 생성자에서 잠그는 것이 아니라 잠금 구조만 생성을 하고 후에 잠그도록 하였습니다.

하지만 transfer 함수가 다음과 같은 코드로 되어 있다면 어떻게 될까요?

void transfer(Box &from, Box &to, int num) {
   //객체 생성과 동시에 lock
   std::unique_lock<std::mutex> lock1(from.m);
   std::unique_lock<std::mutex> lock2(to.m);

   from.num_things -= num;
   to.num_things += num;
}
C++

위와 같은 상황이라면 쓰레드 1(from = acc1, to = acc2)이 from.m(acc1)을 락 걸고 그 다음줄인 to.m(acc2)를 수행하기 전에 쓰레드 2(from = acc2, to = acc1)가 첫줄에서 from.m(acc2)를 건 상황이 될 수 있습니다.
이때 두 쓰레드가 그 다음줄을 수행하려고 하지만 둘다 락이 걸려있는 상태이므로 대기를 하게 됩니다. 하지만 둘다 락을 풀어 주지 않기 때문에 데드락 현상이 발생하게 됩니다.

 

 

 

그래서 이런상환에서는 std:lock을 이용해 동시에 락을 걸어 주어야 타이밍 이슈로 데드락이 발생하지 않습니다.

Reference

https://en.cppreference.com/w/cpp/thread/mutex
https://en.cppreference.com/w/cpp/thread/unique_lock/unique_lock
https://docs.microsoft.com/ko-kr/cpp/standard-library/unique-lock-class?view=vs-2019

 

 

 

 

ref : https://dydtjr1128.github.io/cpp/2020/04/05/Cpp-lock.html

반응형
반응형
I have a base class Media and several derived classes, namely DVD, Book, etc... The base class is written as:
class Media{
    private:
        int id;
        string title;
        int year;
    public:
        Media(){ id = year = 0; title = ""; }
        Media(int _id, string _title, int _year): id(_id), title(_title), year(_year) {}
//      virtual ~Media() = 0;
        void changeID(int newID){ id = newID; }
        virtual void print(ostream &out);
};

The thing is: without the destructor, GCC gives me a bunch of warnings class has virtual functions but non-virtual destructor, but still compiles and my program works fine. Now I want to get rid of those annoying warnings so I satisfy the compiler by adding a virtual destructor, the result is: it doesn't compile, with the error:

undefined reference to `Media::~Media()`

Making the destructor pure virtual doesn't solve the problem. So what has gone wrong?

 

 

 

 

 

 

What you have commented out is a pure-virtual declaration for a destructor. That means the function must be overridden in a derived class to be able to instantiate an object of that class.

What you want is just a definition of the destructor as a virtual function:

virtual ~Media() {}

In C++ 11 or newer, it's generally preferable to define this as defaulted instead of using an empty body:

virtual ~Media() = default;
answered Apr 5, 2012 at 8:05

 

Jerry Coffin
453k76 gold badges597 silver badges

 

 

 

ref : https://stackoverflow.com/questions/10024796/c-virtual-functions-but-no-virtual-destructors

반응형
반응형

멤버 선언에 예기치 않은 한정이 있습니다. 이 경고를 해결하려면 식별자에서 한정자를 제거합니다.

기본적으로 이 경고는 해제되어 있습니다. /Wall 또는 /wN4596을 사용하여 명령줄에서 수준 N 경고로 사용하도록 설정할 수 있습니다. 또는 소스 파일에서 #pragma 경고(N:4596)를 사용합니다. 자세한 내용은 기본적으로 해제되어 있는 컴파일러 경고를참조하세요. 일부 버전의 컴파일러는 /permissive-아래에만 이 경고를 생성합니다.

이 경고는 Visual Studio 2015 업데이트 3부터 사용할 수 있습니다. 이전 버전의 컴파일러에서 경고 없이 컴파일된 코드는 이제 C4596을 생성할 수 있습니다. 특정 컴파일러 버전 이상에 도입된 경고를 사용하지 않도록 설정하는 방법에 대한 자세한 내용은 컴파일러 버전별 컴파일러 경고를참조하세요.

 

 

 

이 샘플에서는 C4596을 생성하고 이를 해결하는 방법을 보여줍니다.

 

// C4596.cpp
// compile with: /w14596 /c

struct A {
    void A::f() { } // error C4596: illegal qualified name in member
                    // declaration.
                    // Remove redundant 'A::' to fix.
};

 

 

ref : https://docs.microsoft.com/ko-kr/cpp/error-messages/compiler-warnings/c4596?view=msvc-170

반응형
반응형

std::move 와 std::forward 는 lvalue, rvalue 의 개념과 엮여서 조금 헷갈린다. 어떻게 동작하는지, 심지어 왜 필요한지 등. 아래의 코드로 간단히 정리해본다.

표면적으로는 std::move 는 주어진 입력인자를 rvalue 로 변환해주는 역할을 한다. 아래의 코드에서와 같이 a 라는 객체는 lvalue 이고 std::move 가 a 를 받고 b 에 리턴하는 것은 rvalue 이다. 이게 다 이다. 이 동작과 move 라는 이름이 무슨 관계란 말인가?

 

이는 rvalue 라는 것과 연관지어서 생각해봐야한다. 클래스는 copy constructor 와 move constructor 를 가질 수 있다. 기본 내장형 또는 custom 형태 든. 보통 copy constructor 는 간단히, 클래스 인스턴스가 가진 모든 것을 복사한다고 생각하면 된다. 따라서 복사 비용이 크다. 반면에 move constructor 는 이렇게 모든 것을 복사할 필요가 없고 따라서 복사비용 (공간 및 시간) 을 효율적으로 해야할 때 사용되는데, 이 move constructor 가 호출되기 위해서는 rvalue 가 필요하다. 그래서 std::move 가 무언가를 ravlue 로 만들어주고 이것이 해당 객체를 'movable' 하도록 만들어주기 때문에 이름이 std::move 인 것이다.

다시한번 생각해봐도 이유가 타당한 것 같다. 어떤 객체가 rvalue 라면, 다시말해 const 의 속성을 가지고 있고 값이 변하지 않는 객체라면, 굳이 비싼 비용을 수반해서 모든 걸 복사할 필요가 없지 않은가?

std::forward 의 경우는 move 와 비슷하나 약간 다르다. std::move 는 입력으로 받은 객체를 무조건 rvalue 로 만들어준다. 그러나 std::forward 는 조건이 맞을 때만 rvalue 로 만들어준다.

※ 참고

- https://www.geeksforgeeks.org/move-constructors-in-c-with-examples/

 

 

Move Constructors in C++ with Examples - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

ref : https://blog.naver.com/sheld2/222654277182

반응형
반응형

td::forward가 가장 많이 쓰이는 곳은 보편 참조 매개변수로 받아서 처리하는 로직입니다.

// lvalue를 캐치하는 함수
void Catch(Person& p, const char* name)
{
    cout << name << "lvalue catch" << endl;
}

// rvalue를 캐치하는 함수
void Catch(Person&& p, const char * name)
{
    cout << name << "rvalue catch" << endl;
}

// 전달받은 obj를 std::forward를 통해서 catch 함수로 전달합니다.
template<typename T>
void ForwardingObj(T&& obj, const char* name)
{
    Catch(std::forward<T>(obj), name);
}

int _tmain(int argc, _TCHAR* argv[])
{
    Person p1("ahn", 1985);
    ForwardingObj(p1, "p1\t\t=\t");
    ForwardingObj(std::move(p1), "std::move(p1)\t=\t");

    return 0;
}

수행결과

 

ref : https://jungwoong.tistory.com/53

반응형
반응형
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
using namespace std;
 
template<typename T>
T f(T a) 
    return a + 1
}
 
 
class A {                            //f 함수 테스트시 A 를 제외하고 생각하면 된다
public:
};
 
template <typename ... Types>
void s1(char cc, Types ... args)
{
 
}
 
template <typename ... Types>
void s1(int cc, Types ... args)
{
 
}
 
template <typename ... Types>
void s1(A cc, Types ... args)
{
 
}
 
template <typename ... Types>
void foo(Types ... args)        //받는건 가변인자로 다 받는데
{
 
    s1(args...);                    //인자 첫번째 타입에 따라 s1 함수가 호출 된다
    //s1(f(args)...);                    //개별 인자들에 +1 을 다 한다음 기반 parameter pack 을 넘긴다
}
 
int main(int argc, char* argv[])
{
    foo('s', A(), 3);
    foo(A(), 's'3);
    foo(12.0f, 3);
    return 0;
}
 
 
cs
반응형

'프로그래밍(Programming) > c++, 11, 14 , 17, 20' 카테고리의 다른 글

[Modern C++] std::move , 간단 예시  (0) 2022.03.03
std::forward  (0) 2022.03.03
std::map::emplace_hint  (0) 2021.03.21
C++ 캐스팅 Static, Dynamic cast  (0) 2020.04.27
std::remove_reference  (0) 2019.12.31
반응형

 

hint 는 추가될 곳에 대한 힌트(위치)를 주는 것

 

template <class... Args> iterator emplace_hint (const_iterator position, Args&&... args);

Construct and insert element with hint

Inserts a new element in the map if its key is unique, with a hint on the insertion position. This new element is constructed in place using args as the arguments for the construction of a value_type (which is an object of a pair type).

The insertion only takes place if no other element in the container has a key equivalent to the one being emplaced (elements in a map container are unique).

If inserted, this effectively increases the container size by one.

The value in position is used as a hint on the insertion point. The element will nevertheless be inserted at its corresponding position following the order described by its internal comparison object, but this hint is used by the function to begin its search for the insertion point, speeding up the process considerably when the actual insertion point is either position or close to it.

The element is constructed in-place by calling allocator_traits::construct with args forwarded.

Parameters

positionHint for the position where the element can be inserted.
The function optimizes its insertion time if position points to the element that will follow the inserted element (or to the end, if it would be the last).
Notice that this does not force the new element to be in that position within the map container (the elements in a map always follow a specific order).
const_iterator is a member type, defined as a bidirectional iterator type that points to elements.args

Arguments forwarded to construct the new element (of type pair<const key_type, mapped_type>).
This can be one of:
- Two arguments: one for the key, the other for the mapped value.
- A single argument of a pair type with a value for the key as first member, and a value for the mapped value as second.
- piecewise_construct as first argument, and two additional arguments with tuples to be forwarded as arguments for the key value and for the mapped value respectivelly.
See pair::pair for more info.

 

Return value

If the function successfully inserts the element (because no equivalent element existed already in the map), the function returns an iterator to the newly inserted element.

Otherwise, it returns an iterator to the equivalent element within the container.

Member type iterator is a bidirectional iterator type that points to an element.

 

 

Complexity

Generally, logarithmic in the container size.
Amortized constant if the insertion point for the element is position.

 

 

www.cplusplus.com/reference/map/map/emplace_hint/

반응형
반응형


정적 캐스팅  static_cast  컴파일 타임에 잡아준다
 
 dynamic_cast 실행중에 잡아준다
 
 
static cast 의 경우 값 변환시 값을 유지할려고 한다(반올림 오차는 제외_, 이진수 표기는 달라질수 있음, ex 소수를 정수로 바구는 경우)
 
 컴파일 도중에 해준다 static cast 는 상속 관계만 따져서 상속관계에 있다면 컴파일 됨(실행중에는 문제가 생길수 있음)
 즉 완전 다른 상속관계의 캐스팅은 컴파일 에러를 벹는다
 실행도중 크래시 날수 있음
 변수형 체크 후 베이스 클래스를 파생 클래스로 변환시
 
 
 
 
 
 #include


class Animal
{
public:
Animal(){}

virtual void fn() {

}

private:

};

class Cat : public Animal
{
public:

private:

};

class Dog : public Animal
{
public:

private:

};

class AAA {

};


int main()
{

/*
정적 캐스팅
static_cast  컴파일 타임에 잡아준다

dynamic_cast 실행중에 잡아준다


static cast 의 경우 값 변환시 값을 유지할려고 한다(반올림 오차는 제외_, 이진수 표기는 달라질수 있음, ex 소수를 정수로 바구는 경우)

컴파일 도중에 해준다 static cast 는 상속 관계만 따져서 상속관계에 있다면 컴파일 됨(실행중에는 문제가 생길수 있음)
즉 완전 다른 상속관계의 캐스팅은 컴파일 에러를 벹는다
실행도중 크래시 날수 있음
변수형 체크 후 베이슼 ㅡㄹ래스를 파생 클래스로 변환시

*/


//static cast 로 값을 변할 할 경우 값을 유지하려고 한다 2진수 표기는 달라질 수 있음
int intVal = 30;
float fdd = static_cast(intVal); //컴파일 가능
char dd = static_cast(intVal); //컴파일 가능
//char* dd1 = static_cast<char*>(intVal); //컴파일 에러

Animal* animalCat = new Cat;
Cat* myCat = static_cast<Cat*>(animalCat);

Dog* ps = static_cast<Dog*>(animalCat); //static 은 상속관계만 따지기 때문에 에러가 안난다 animalCat 은 Animal 클래스를 부모로 두고 있는데 Dog 는 Animal 을 상속 받음으로, 그러나 위험함

//AAA* aaa = static_cast<AAA*>(animalCat); //상속관계가 없다면 컴파일 에러

//Dog* psd = dynamic_cast<Dog*>(animalCat); //이때는 컴파일 에러를 벹음

Dog* psd = dynamic_cast<Dog*>(animalCat); //!!!virtual 키워드가 없다면 이때는 컴파일 에러를 벹음
//, virtual 키워드가 있다면 컴파일 성공함, 그리고 null을 반환한다 
//dynamic cast 는 RTTI 가 켜 있어야 작동하는데 이것이 꺼져 있다면 static_cast 와 동일하게 동작한다



int b= 10;
int c = static_cast(b);
//unsigned int* c2 = static_cast(&b); //컴파일 에러
//unsigned int* c2 = reinterpret_cast(&b); //컴파일 가능
//int a = reinterpret_cast(b); //컴파일 에러
//Cat* catP = reinterpret_cast<Cat*>(&b); //컴파일 가능


/*
const_cast 는 (포인터의 상수성)만을 변경하고자 할때 사용한다!!!
값에는 안쓰이는데 값은 어차피 복사 되는거니깐 안전함
변수를 선언할 때 앞에 volatile을 붙이면 컴파일러는 해당 변수를 최적화에서 제외하여 항상 메모리에 접근하도록 만듭니다.

const_cast  로는 형을 바꿀 수는 없고 const 또는 volatile 애트리뷰트를 제거할때 사용한다
*/


//int iii = 30;
//const int ddd= const_cast(iii); //컴파일 에러

const int iii2 = 30;
//int ddd = const_cast(iii2); //컴파일 에러, 포인터가 아님
int* ddd = const_cast<int*>(&iii2); //컴파일 가능
const int* ddd3 = const_cast(&iii2); //컴파일 가능

int* tly = nullptr;
const int* ddd7 = const_cast(ddd); //컴파일 성공 const 를 붙여준다

//Animal* animalCat = new Cat;
//const Cat* myCat2 = static_cast(animalCat); //컴파일 가능
//const Cat* myCat2 = const_cast(animalCat); //const cast 는 형변환을 허용하지 않는다


return 0;
}

반응형
반응형

 

 

Defined in header <type_traits>

template< class T >
struct remove_reference;

(since C++11)

If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T.

Member types

Name Definition
type the type referred by T or T if it is not a reference

Helper types

template< class T >
using remove_reference_t = typename remove_reference<T>::type;

(since C++14)

Possible implementation

Example

true

false

false

true

true

true

 

 

ref : https://en.cppreference.com/w/cpp/types/remove_reference

반응형
반응형

0. Variadic template

C++11의 emplace 함수에 생긴 큰 변화는 variadic template으로 인해 가능해졌다고 할 수 있다.
Variaidic template 페이지를 충분히 숙지한 이후 아래 내용을 읽어 나가면 훨씬 이해가 쉬울 것이다.


1. C++0x의 emplace 함수

VS2010까지의 emplace 함수들은 단순히 rvalue-reference를 인자로 받아 기존의 추가 함수를 호출하는 형식이었다,

  1. // VS2010의 vector::emplace_back
  2. void emplace_back(_Ty&& _Val)
  3. {      
  4.     // insert element at end
  5.     push_back(_STD forward<_Ty>(_Val));
  6. }
  7.  
  8. template<typename _Valty>
  9. void emplace_back(_Valty&& _Val)
  10. {      
  11.     // insert element at end
  12.     if (this->_Mylast == this->_Myend)
  13.         _Reserve(1);
  14.     _Orphan_range(this->_Mylast, this->_Mylast);
  15.                
  16.     _Cons_val(this->_Alval, this->_Mylast, _STD forward<_Valty>(_Val));
  17.     ++this->_Mylast;
  18. }

즉, C++0x까지의 emplace 계열 함수는 외부에서 생성된 객체를 넘기는 방식이었기에, 
emplace 계열 함수를 쓰더라도, 외부에서 객체 생성 -> 이동 -> 객체 파괴가 이루어졌던 것이다.

물론, 객체 생성 -> 복사 -> 객체 파괴보다는 성능상 이득이 있을 수 있다.
(이동을 어떻게 짰느냐에 따라...)


2. C++11의 emplace 함수 

emplace 함수들이 variadic template으로 인해 진정한 emplace 계열 함수로 거듭나게 되었다.

  1. // VS2013의 vector::emplace_back
  2. template<typename... _Valty>
  3. void emplace_back(_Valty&&... _Val)
  4. {
  5.     // insert by moving into element at end
  6.     if (this->_Mylast == this->_Myend)
  7.         _Reserve(1);
  8.     _Orphan_range(this->_Mylast, this->_Mylast);
  9.  
  10.     this->_Getal().construct(this->_Mylast, _STD forward<_Valty>(_Val)...);            
  11.     ++this->_Mylast;
  12. }

C++11의 emplace 계열 함수는 객체 생성자의 인자들을 넘겨,
컨테이너 내부에서 생성 후 추가하는 방식을 사용하기에, 임시 객체를 아예 생기지 않게 하거나, 그 횟수를 줄일 수 있다.

우선 vector의 emplace_back 함수의 예제를 살펴보자. (cppreference.com의 예제 도용 ㅋ)
아래 예제에서는 임시 객체 생성을 완전히 회피할 수 있다.

  1. #include <vector>
  2. #include <string>
  3. #include <iostream>
  4.  
  5. using namespace std;
  6.  
  7. struct President
  8. {
  9.     string name;
  10.     string country;
  11.     int year;
  12.  
  13.     President(string p_name, string p_country, int p_year)
  14.         : name(move(p_name)), country(move(p_country)), year(p_year)
  15.     {
  16.         cout << "I am being constructed.\n";
  17.     }
  18.  
  19.     President(const President& other)
  20.         : name(move(other.name)), country(move(other.country)), year(other.year)
  21.     {
  22.         cout << "I am being copied.\n";
  23.     }
  24.  
  25.     President(President&& other)
  26.         : name(move(other.name)), country(move(other.country)), year(other.year)
  27.     {
  28.         cout << "I am being moved.\n";
  29.     }
  30.  
  31.     ~President()
  32.     {
  33.         cout << "I am being destructed.\n";
  34.     }
  35.  
  36.     President& operator=(const President& other) = default;
  37. };
  38.  
  39. int main()
  40. {
  41.     // VS2013의 emplace_back
  42.     // vector 내부에서 생성 -> 컨테이터에 추가하기에 임시 객체 생성 X
  43.     vector<President> elections;
  44.     elections.emplace_back("Nelson Mandela""South Africa"1994);
  45.  
  46.     // VS2010의 emplace_back 역시 아래 push_back과 동일한 방식으로만 사용이 가능했었다
  47.     // 외부에서 생성 -> 벡터로 이동 -> 외부 객체 파괴가 발생한다
  48.     vector<President> reElections;
  49.     reElections.push_back(President("Franklin Delano Roosevelt""the USA"1936));
  50.  
  51.     for (President const& president: elections)
  52.     {
  53.         cout << president.name << " was elected president of "
  54.              << president.country << " in " << president.year << ".\n";
  55.     }
  56.     for (President const& president: reElections)
  57.     {
  58.         cout << president.name << " was re-elected president of "
  59.              << president.country << " in " << president.year << ".\n";
  60.     }
  61. }

std::map의 emplace 함수도 예제를 한번 살펴 보도록 하자.
아래 예제에서는 임시 객체가 한 번 덜 생성되는 것을 확인할 수 있다.

  1. typedef map<int, President> ElectionMap;
  2. ElectionMap elections;
  3.  
  4. ////////////////////////////////////////////////////////////////////////////////
  5. /// 기존의 insert
  6. ////////////////////////////////////////////////////////////////////////////////
  7. {
  8.     // p 객체 생성
  9.     President p("Nelson Mandela""South Africa"1994);
  10.  
  11.     // p 객체 복사 생성 for pair -> p 객체 이동
  12.     // 아래 두 문장은 동일하다
  13.     //elections.insert(ElectionMap::value_type(1, p));
  14.     elections.insert(make_pair(1, p));
  15.  
  16.     // 이 스코프가 종료되면,
  17.     // President p("Nelson Mandela", "South Africa", 1994);에서 생성된 객체 파괴
  18.     // ElectionMap::value_type(1, p)에서 생성된 임시 객체 파괴
  19.     // map 소멸되면서 보관되어 있던 원소 파괴
  20. }
  21.  
  22. ////////////////////////////////////////////////////////////////////////////////
  23. /// C++11의 emplace
  24. ////////////////////////////////////////////////////////////////////////////////
  25. {
  26.     // President 객체 생성 -> 객체 이동 후 바로 컨테이너에 추가
  27.     elections.emplace(1, President("Nelson Mandela""South Africa"1994));
  28.  
  29.     // 이 스코프가 종료되면
  30.     // President("Nelson Mandela", "South Africa", 1994)에서 생성된 객체 파괴
  31.     // map 소멸되면서 보관되어 있던 원소 파괴
  32. }

참고로, 각 STL 컨테이너들이 지원하는 emplace 계열 함수들은 다음과 같다.

1) vector
  • emplace
  • emplace_back
2) deque / list
  • emplace
  • emplace_back
  • emplace_front
3) foward_list
  • emplace_front
  • emplace_after
4) set / map / unoreder_set / unordered_map
  • emplace
  • emplace_hint



ref : http://egloos.zum.com/sweeper/v/3060229


반응형
반응형


VS2017 기준 : C++ 17 활성화 옵션 



 프로젝트 속성페이제어서 다음처럼 원하는 C++ 버전을 선택해주면 된다










Add to Additional options in Project Settings: /std:c++latest to enable latest features - currently C++17 as of VS2017, VS2015 Update 3.

https://blogs.msdn.microsoft.com/vcblog/2016/06/07/standards-version-switches-in-the-compiler/

/permissive- will disable non-standard C++ extensions and will enable standard conformance in VS2017.

https://blogs.msdn.microsoft.com/vcblog/2016/11/16/permissive-switch/

EDIT (Nov 2017): Latest VS2017 features are documented here: https://docs.microsoft.com/en-gb/cpp/build/reference/std-specify-language-standard-version

VS2017 supports now: /std:[c++14|c++17|c++latest] and these flags can be set via the project's property pages:

To set this compiler option in the Visual Studio development environment

  1. Open the project's Property Pages dialog box. For details, see Working with Project Properties.
  2. Select Configuration Properties, C/C++, Language.
  3. In C++ Language Standard, choose the language standard to support from the dropdown control, then choose OK or Apply to save your changes.


https://stackoverflow.com/questions/41308933/how-to-enable-c17-compiling-in-visual-studio

반응형
반응형


Rvalue References: C++0x Features in VC10, Part 2



Today, I’m going to talk about rvalue references, which enable two different things: move semantics and perfect forwarding.  This post will be long, because I’m going to explain how rvalue references work in great detail.  They’re initially very confusing because they distinguish lvalues from rvalues, which very few C++98/03 programmers are extensively familiar with.

 

Fear not, for using rvalue references is easy, much easier than it initially sounds.  Implementing either move semantics or perfect forwarding in your own code boils down to following simple patterns, which I will demonstrate.  And it’s definitely worth learning how to use rvalue references, as move semantics can produce order of magnitude performance improvements, and perfect forwarding makes writing highly generic code very easy.

 

 

lvalues and rvalues in C++98/03

 

In order to understand rvalue references in C++0x, you must first understand lvalues and rvalues in C++98/03.

 

The terminology of “lvalues” and “rvalues” is confusing because their history is confusing.  (By the way, they’re just pronounced as “L values” and “R values”, although they’re written as single words.)  These concepts originally came from C, and then were elaborated upon by C++.  To save time, I’ll skip over their history, including why they’re called “lvalues” and “rvalues”, and I’ll go directly to how they work in C++98/03.  (Okay, it’s not a big secret: “L” stands for “left” and “R” stands for “right”.  But the concepts have evolved since the names were chosen, and the names aren’t very accurate anymore.  Instead of going through the whole history lesson, you can consider the names to be arbitrary like “up quark” and “down quark”, and you won’t lose anything.)

 

C++03 3.10/1 says: “Every expression is either an lvalue or an rvalue.”  It’s important to remember that lvalueness versus rvalueness is a property of expressions, not of objects.

 

Lvalues name objects that persist beyond a single expression.  For example, obj , *ptr , ptr[index] , and ++x are all lvalues.

 

Rvalues are temporaries that evaporate at the end of the full-expression in which they live (“at the semicolon”).  For example, 1729 , x + y , std::string(“meow”) , and x++ are all rvalues.

 

Notice the difference between ++x and x++ .  If we have int x = 0; then the expression x is an lvalue, as it names a persistent object.  The expression ++x is also an lvalue.  It modifies and then names the persistent object.  However, the expression x++ is an rvalue.  It copies the original value of the persistent object, modifies the persistent object, and then returns the copy.  This copy is a temporary.  Both ++x and x++increment x, but ++x returns the persistent object itself, while x++ returns a temporary copy.  That’s why ++x is an lvalue, while x++ is an rvalue.  Lvalueness versus rvalueness doesn’t care about what an expression does, it cares about what an expression names (something persistent or something temporary).

 

If you want to build up intuition for this, another way to determine whether an expression is an lvalue is to ask “can I take its address?”.  If you can, it’s an lvalue.  If you can’t, it’s an rvalue.  For example, &obj , &*ptr , &ptr[index] , and &++x are all valid (even though some of those expressions are silly), while &1729 , &(x + y) , &std::string(“meow”) , and &x++ are all invalid.  Why does this work?  The address-of operator requires that its “operand shall be an lvalue” (C++03 5.3.1/2).  Why does it require that?  Taking the address of a persistent object is fine, but taking the address of a temporary would be extremely dangerous, because temporaries evaporate quickly.

 

The preceding examples ignore operator overloading, which is convenient syntax for a function call.  “A function call is an lvalue if and only if the result type is a reference.” (C++03 5.2.2/10)  Therefore, given vector<int> v(10, 1729); , v[0] is an lvalue because operator[]()returns int& (and &v[0] is valid and useful), while given string s(“foo”); and string t(“bar”); , s + t is an rvalue because operator+() returns string (and &(s + t) is invalid).

 


 

Type& binds to modifiable lvalues (and can be used to observe and mutate them).  It can’t bind to const lvalues, as that would violate constcorrectness.  It can’t bind to modifiable rvalues, as that would be extremely dangerous.  Accidentally modifying temporaries, only to have the temporaries evaporate along with your modifications, would lead to subtle and obnoxious bugs, so C++ rightly prohibits this.  (I should mention that VC has an evil extension that allows this, but if you compile with /W4 , it warns when the evil extension is activated.  Usually.)  And it can’t bind to const rvalues, as that would be doubly bad.  (Careful readers should note that I’m not talking about template argument deduction here.)

 

const Type& binds to everything: modifiable lvalues, const lvalues, modifiable rvalues, and const rvalues (and can be used to observe them).

 

A reference is a name, so a reference bound to an rvalue is itself an lvalue (yes, L).  (As only a const reference can be bound to an rvalue, it will be a const lvalue.)  This is confusing, and will be an extremely big deal later, so I’ll explain further.  Given the function void observe(const string& str) , inside observe()‘s implementation, str is a const lvalue, and its address can be taken and used before observe() returns.  This is true even though observe() can be called with rvalues, such as three() or four() above.  observe(“purr”) can also be called, which constructs a temporary string and binds str to that temporary.  The return values of three() and four() don’t have names, so they’re rvalues, but within observe()str is a name, so it’s an lvalue.  As I said above, “lvalueness versus rvalueness is a property of expressions, not of objects”.  Of course, because str can be bound to a temporary which will evaporate, its address shouldn’t be stored anywhere where it could be used after observe() returns.

 

Have you ever bound an rvalue to a const reference and then taken its address?  Yes, you have!  This is what happens when you write a copy assignment operator, Foo& operator=(const Foo& other) , with a self-assignment check, if (this != &other) { copy stuff; } return *this; , and you copy assign from a temporary, like Foo make_foo(); Foo f; f = make_foo(); .

 

At this point, you might ask, “So what’s the difference between modifiable rvalues and const rvalues?  I can’t bind Type& to modifiable rvalues, and I can’t assign things (etc.) to modifiable rvalues, so can I really modify them?”  This is a very good question!  In C++98/03, the answer is that there’s a slight difference: non-const member functions can be called on modifiable rvalues.  C++ doesn’t want you to accidentally modify temporaries, but directly calling a non-const member function on a modifiable rvalue is explicit, so it’s allowed.  In C++0x, the answer changes dramatically, making move semantics possible.

 

Congratulations!  Now you have what I call “lvalue/rvalue vision”, the ability to look at an expression and determine whether it’s an lvalue or an rvalue.  Combined with your “const vision”, you can precisely reason that given void mutate(string& ref) and the definitions above, mutate(one) is valid, while mutate(two)mutate(three())mutate(four()), and mutate(“purr”) are invalid, and all of observe(one)observe(two)observe(three())observe(four()), and observe(“purr”) are valid.  If you’re a C++98/03 programmer, you already knew which of these calls were valid and which were invalid; your “gut feeling”, if not your compiler, would have told you that mutate(three()) was bogus.  Your new lvalue/rvalue vision tells you precisely why (three() is an rvalue, and modifiable references can’t be bound to rvalues).  Is that useful?  To language lawyers, yes, but not really to normal programmers.  After all, you’ve gotten this far without knowing all of this stuff about lvalues and rvalues.  But here’s the catch: compared to C++98/03, C++0x has vastly more powerful lvalue/rvalue vision (in particular, the ability to look at an expression, determine whether it’s a modifiable/const lvalue/rvalue, and do something about it).  In order to use C++0x effectively, you need lvalue/rvalue vision too.  And now you have it, so we can proceed!

 

 

the copying problem

 

C++98/03 combines insanely powerful abstraction with insanely efficient execution, but it has a problem: it’s overly fond of copying.  Things with value semantics behave like ints, so copying a thing doesn’t modify the source, and the resulting copies are independent.  Value semantics are great, except that they tend to lead to unnecessary copies of heavy objects like strings, vectors, and so forth.  (“Heavy” means “expensive to copy”; a million-element vector is heavy.)  The Return Value Optimization (RVO) and Named Return Value Optimization (NRVO), where copy constructors are elided in certain situations, help to alleviate this problem, but they don’t remove all unnecessary copies.

 

The most unnecessary copies are those where the source is about to be destroyed.  Would you photocopy a sheet of paper and then immediately throw away the original, assuming that the original and the photocopy are identical?  That would be wasteful; you should keep the original and not bother with the photocopy.  Here’s what I call “the killer example”, derived from one of the Standardization Committee’s examples (in N1377).  Suppose that you have a bunch of strings, like this:

 

string s0(“my mother told me that”);

string s1(“cute”);

string s2(“fluffy”);

string s3(“kittens”);

string s4(“are an essential part of a healthy diet”);

 

And that you concatenate them like this:

 

string dest = s0 + ” ” + s1 + ” ” + s2 + ” ” + s3 + ” ” + s4;

 

How efficient is this?  (We’re not worrying about this specific example, which executes in microseconds; we’re worrying about its generalization, which occurs throughout the entire language.)

 

Each call to operator+() returns a temporary string.  There are 8 calls to operator+() , so there are 8 temporary strings.  Each one, upon its construction, performs a dynamic memory allocation and copies all of the characters that have been concatenated so far, and later, upon its destruction, performs a dynamic memory deallocation.  (If you’ve heard of the Small String Optimization, which VC performs in order to avoid dynamic memory allocations and deallocations for short strings, it’s defeated here by my carefully chosen and sufficiently long s0 , and even if it applied, it couldn’t avoid the copying.  If you’ve heard of the Copy-On-Write “optimization”, forget about it – it doesn’t apply here, and it’s a pessimization under multithreading, so Standard Library implementations don’t do it anymore.)

 

In fact, because every concatenation copies all of the characters that have been concatenated so far, this has quadratic complexity in the number of concatenations.  Yuck!  This is extraordinarily wasteful, which is especially embarrassing for C++.  Why is this happening, and what can we do about it?

 

The problem is that operator+() , which takes two const string& or one const string& and one const char * (there are other overloads, which we aren’t using here), can’t tell whether it’s being fed lvalues versus rvalues, so it always has to create and return a new temporary string .  Why do lvalues versus rvalues matter?

 

When evaluating s0 + ” “ , it’s absolutely necessary to create a new temporary string .  s0 is an lvalue, naming a persistent object, so we can’t modify it.  (Someone would notice!)  But when evaluating (s0 + ” “) + s1 , we could simply append s1‘s contents onto our first temporary string, instead of creating a second temporary and throwing the first temporary away.  This is the key insight behind move semantics: because s0 + ” “ is an rvalue, an expression referring to a temporary object, no one else in the entire program can observe that temporary object.  If we could detect that expression as being a modifiable rvalue, we could then proceed to modify the temporary object arbitrarily, without anyone else noticing.  operator+() isn’t “supposed to” modify its arguments, but if they’re modifiable rvalues, who cares?  In this manner, each call to operator+() can append characters onto a single temporary string .  This completely eliminates the unnecessary dynamic memory management and unnecessary copying, leaving us with linear complexity.  Yay!

 

Technically speaking, in C++0x, each call to operator+() still returns a separate temporary string .  However, the second temporary string(from evaluating (s0 + ” “) + s1 ) is constructed by stealing the memory owned by the first temporary string (from evaluating s0 + ” “) and then appending s1‘s contents onto that memory (which may trigger an ordinary geometric reallocation).  “Stealing” consists of pointer twiddling: the second temporary copies and then nulls out the first temporary’s internal pointer.  When the first temporary is eventually destroyed (“at the semicolon”), its pointer is null, so its destructor does nothing.

 

In general, being able to detect modifiable rvalues allows you to engage in “resource pilfering”.  If the objects referred to by modifiable rvalues own any resources (such as memory), you can steal their resources instead of copying them, since they’re going to evaporate anyways.  Constructing from or assigning from modifiable rvalues by taking what they own is generically referred to as “moving”, and moveable objects have “move semantics”.

 

This is extremely useful in many places, such as vector reallocation.  When a vector needs more capacity (e.g. during push_back()) and undergoes reallocation, it needs to copy elements from



ref : https://blogs.msdn.microsoft.com/vcblog/2009/02/03/rvalue-references-c0x-features-in-vc10-part-2/

반응형
반응형



결과는 컴파일러나 모드에 따라 다를 수 있지만 전체 문맥적으로 본다면 이해하는데 큰 무리는 없을겁니다





C/C++를 써야만 하는 혹은 쓸 수 밖에 없는 상황들이라는 것은 대부분 성능,속도, 자원 사용의 최적화 등이지요. Visual Studio 2010에 새로이 추가된 Rvalue concept 이나 사용 예들도 정확히 이러한 상황들에 대응하기 위한 것이라 할 수 있을 겁니다.

Rvalue의 경우에도 class의 개발 시 move constructor/operator 를 추가하여,  method를 호출할 때, 매개변수를 통하여 객체를 전달하는 과정에서 임시객체의 생성/소멸을 가능한 막아보자는 것이지요. 하지만 이러한 최적화 과정은 사실 Rvalue concept의 도입이전에도 다양한 형태로 시도되고, 실제 컴파일러에 적용되었습니다. 오늘 설명 드리고자 하는 (N)RVO 도 Rvalue와 비슷한 맥락의 최적화 기법 중 하나입니다.

간단한 예제를 통해서 그 내용을 살펴 볼까 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
 
class RVO
{
public:
    RVO()
    {
        printf("I am in constructor\n");
    }
 
    RVO (const RVO& c_RVO) 
    {
        printf ("I am in copy constructor\n");
    }
    ~RVO()
    {
        printf ("I am in destructor\n");
    }
 
    int mem_var;       
};
 
RVO MyMethod (int i)
{
    RVO rvo;
    rvo.mem_var = i;
    return (rvo);
}
 
int _tmain(void)
{
    RVO rvo;
    rvo = MyMethod(5);
 
    return 0;
}




위 예제를 머리 속으로 컴파일 해서 수행해 보시고 출력 결과가 어떠할지, 그리고 생성자, 복사 생성자, 파괴자가 각각 몇 번이나 호출될지 정확히 예측하실 수 있으실까요? 위 source를 어떠한 optimiation도 켜지 않은 상태로, debug mode에서 수행하면 다음과 같은 결과를 출력합니다.


I am in constructor         <-- _tmain의 RVO rvo; 행에 의해서 호출될 겁니다. 
I am in constructor <-- MyMethod 함수의 RVO rvo; 행에 의해서 호출될 겁니다.
I am in copy constructor  <-- 이 녀석은 어디서 호출되는 걸까요? MeThod 함수의 return (rvo); 에서 호출됩니다.
I am in destructor
I am in destructor
I am in destructor

생성자, 복사 생성자, 파괴자는 총 6회 호출 되었음을 알 수 있죠. 어디서 생성자, 복사 생성자, 파괴자가 생성되었는지를 좀 더 명확하게 살펴보려면 위 코드를 컴파일 한 후 assembly code를 드려다 보면 좋습니다. 

먼저 _tmain() 부터 확인해보죠.


    RVO rvo; 

00F41518  lea         ecx,[ebp-10h]  // rvo의 위치는 [ebp-10h] 입니다. 
00F4151B  call        RVO::RVO (0F41140h)  // 생성자가 호출되었군요 1 
00F41520  mov         dword ptr [ebp-4],0  
    rvo = MyMethod(5); 
00F41527  push        5  
00F41529  lea         eax,[ebp-58h]  
// 5는 [ebp-58h]에 저장됩니다. 
00F4152C  push        eax  
// stack에 push하구요. 
00F4152D  call        MyMethod (0F410DCh)  // 함수를 호출했습니다. 
00F41532  add         esp,8  // stack을 정리 했구요. 
00F41535  mov         dword ptr [ebp-5Ch],eax  // 반환 값을 [ebp-5ch]로 담고는 
00F41538  mov         ecx,dword ptr [ebp-5Ch]  // 그 값을 다시 ecx로 옮깁니다. 
00F4153B  mov         edx,dword ptr [ecx]    // ecx가 가리키는 메모리의 값을 edx로 옮기고 
00F4153D  mov         dword ptr [ebp-10h],edx  // rvo 변수가 가리킬 수 있도록 변경합니다. 
00F41540  lea         ecx,[ebp-58h]  // 이건 반환되었던 객체죠? 
00F41543  call        RVO::~RVO (0F4101Eh) // 반환 되었던 객체의 파괴자를 호출합니다. 5

    return 0; 
00F41548  mov         dword ptr [ebp-54h],0  
00F4154F  mov         dword ptr [ebp-4],0FFFFFFFFh  
00F41556  lea         ecx,[ebp-10h]  // rvo 입니다. 
00F41559  call        RVO::~RVO (0F4101Eh) // rvo에 대한 파괴자를 호출하는군요. 6 
00F4155E  mov         eax,dword ptr [ebp-54h]  


이젠 MyMethod도 살펴 보겠습니다.



    RVO rvo; 
00F413EF  lea         ecx,[ebp-10h]  // rvo의 위치는 [ebp-10h] 입니다. 
00F413F2  call        RVO::RVO (0F41140h)  // 생성자가 호출되었군요. 2 
00F413F7  mov         dword ptr [ebp-4],1  
    rvo.mem_var = i; 
00F413FE  mov         eax,dword ptr [ebp+0Ch]  
00F41401  mov         dword ptr [ebp-10h],eax  
    return (rvo); 
00F41404  lea         eax,[ebp-10h]  
00F41407  push        eax  
00F41408  mov         ecx,dword ptr [ebp+8]  // [ebp+8] 위치의 임시 객체에 대해서 
00F4140B  call        RVO::RVO (0F41145h)  // 복사 생성자를 호출하는군요. 3 
00F41410  mov         ecx,dword ptr [ebp-54h]  
00F41413  or          ecx,1  
00F41416  mov         dword ptr [ebp-54h],ecx  
00F41419  mov         byte ptr [ebp-4],0  
00F4141D  lea         ecx,[ebp-10h]  // [ebp-10h] 위치의 객체는 rvo 입니다. 
00F41420  call        RVO::~RVO (0F4101Eh) // 파괴자를 호출하는군요 4
 
00F41425  mov         eax,dword ptr [ebp+8]  
}


(복사)생성자, 파괴자의 호출 code는 붉은색으로 표시하였고, 옆에 호출 순서에 따라 번호를 써 두었습니다. 근데 여기서 우리가 유심히 살펴보았음 직한 녀석은 3번의 복사 생성자 호출과 5번의 파괴자 호출입니다. 조금만 고민해 보면 MyMethod()에서 객체를 반환하기 위해서 임시 객체를 생성하고, 생성된 임시 객체를 반환한 후, 생성된 임시 객체의 파괴자를 호출하는 일련의 과정은 없어도 될 것 같지 않으세요? 이미 _tmain에서 생성된 객체가 있으므로, 임시 객체의 생성/파괴는 사실 불필요 하죠. 

임시 객체를 생성해야 했던 이유는 함수의 반환 값으로 객체를 전달하기 위해서만 쓰였잖아요. 이제 제가 말씀 드리고 싶어한 걸 설명할 시간이 되었네요. 

RVO는 Return Value Optimization 이라는 기법인데요. 이것이 뭔고 하니 위 예와 같이 어떤 함수가 객체를 반환해야 할 경우에 필요하지 않은 임시 객체를 생성하지 않도록 최적화 하는걸 말합니다. 그런데 RVO는 MyMethod() 에서 처럼 반환 객체가 변수명을 가지는 경우는 최적화가 되질 않았어요. 그래서 몇몇 사람들이 “변수가 이름을 가지는 경우에도 최적화의 대상에 포함시키자”로 주장했고, 이를 NRVO, Named Return Value Optimization 이라고 구분하여 불렀습니다. 그리하여 ISO/ANSI C++ 위원회에서 1996년에 이 이 둘 모두에 대해서 최적화 될 수 있음을 발표했다고 하는군요.(사실 발표하는 것이 뭐 어렵습니까? compiler 개발사만 어렵지..) 여하둥둥 그리하여 Visual Studio 2005에서 NRVO에 대한 최적화 기능이 포함되었습니다.


Visual Studio에서 NRVO를 가능하게 하기 위해서는 /O2 compiler option을 주면 됩니다.
(프로젝트 설정에서 Optimization : Maximize Speed를 선택하시면 됩니다.) 


vs2017 에서 Release 모드인경우 O2가 default 

Debug mode 에서는 '사용 않함 /Od' 가 기본 모드

이제 최적화의 결과물을 살펴 보시죠. 

I am in constructor 
I am in constructor 
I am in destructor 
I am in destructor


위의 출력결과와 사뭇 다른 것은 복사생성자의 호출이 빠졌고, 파괴자의 호출이 하나 줄어들었음을 알 수 있습니다.
(어느 부분이 생략되었는지 예측하실 수 있겠죠?)




내용을 명확하게 하기 위해서 컴파일된 결과물을 disassmebly 한 결과


MyMethod() 입니다.


RVO MyMethod (int i) 

    RVO rvo; 
01351030  push        esi  
01351031  call        RVO::RVO (1351000h)  // rvo 객체 생성자 호출이 여기 있군요. 
    rvo.mem_var = i; 
01351036  mov         dword ptr [esi],5  // 여기도 중요하죠. esi를 통해서 전달된 객체에다가 5를 넣어버립니다. 객체생성이 없죠. 
    return (rvo); 
0135103C  mov         eax,esi  

0135103E  ret 




_tmain() 입니다.



    RVO rvo; 
01351045  lea         eax,[rvo]  
01351048  push        eax  
01351049  call        RVO::RVO (1351000h)  // rvo 객체 생성자 호출이지요 
    rvo = MyMethod(5); 
0135104E  lea         esi,[rvo]  // 여기가 핵심입니다. 함수의 호출 결과를 rvo로 할당하는게 아니라,
                             rvo 값을 esi를 통해서 MyMethod로 넘겨버립니다.
 
01351051  call        MyMethod (1351030h)  
01351056  call        RVO::~RVO (1351020h)  // 파괴자 호출

    return 0; 
0135105B  call        RVO::~RVO (1351020h)  // 파괴자 호출 
01351060  xor         eax,eax  
01351062  pop         esi  

01351063  mov         esp,ebp  
01351065  pop         ebp  
01351066  ret  



여기저기 최적화 루틴 때문에, 앞서의 코드와는 많이 달라졌지만, 어떤 식으로 최적화가 진행되었는지를 미루어 짐작해 볼 수 있습니다. 







1. RVO

MEC++ 20항목에 나오는 예제를 그대로 활용해보자.


  1. class Rational
  2. {
  3. public:
  4.         Rational(int numerator = 0int denominator = 1);
  5.         ...
  6.         int numerator() const;
  7.         int denominator() const;
  8. };
  9.  
  10. const Rational operator * (const Rational &lhs, const Rational &rhs);

위에서 operator * 는 Rational 객체를 값으로 (const) 반환하고 있다.
객체를 값으로 반환하기 때문에 임시 객체 생성이 불가피한 것이다.


  1. // 1.
  2. const Rational* operator * (const Rational &lhs, const Rational &rhs);
  3. // 2.
  4. const Rational& operator * (const Rational &lhs, const Rational &rhs);


객체를 값으로 반환하는 함수에 대해 원론적으로 임시 객체 생성을 피할 수는 없다.
다만, 최신의 컴파일러들이 지원하는 RVO 최적화를 이용하는 길이 있을 뿐이다.

최적화를 이용하는 그 방법은 바로 객체 대신에 객체 생성자 인자를 반환하는 것이다.

  1. inline const Rational operator * (const Rational &lhs, const Rational &rhs)
  2. {
  3.         return Rational(lhs.numerator * rhs.numerator,
  4.                         lhs.denominator * rhs.denominator);
  5. }

얼핏 보기에 위 코드가 무슨 이득이 있나 싶다.

operator * 안에서 객체가 1번 만들어지고 다시 이것을 반환하면서 임시 객체까지 만들어지니
오히려 손해일 것 같다는 느낌이 팍팍 드는데 말이다.

하지만, 컴파일러는 반환시 임시 객체를 없애고,
계산 결과값을 반환값을 받는 객체에 대해 할당된 메모리에 직접 넣어 초기화해 준다.
결국 생성자 한번, 소멸자 한번의 호출 비용만 들어가는 것이다.

이것이 바로 RVO(Return Value Optimization)인 것이다.

RVO는 단독 형태의 연산자 오버로딩 구현에도 톡톡히 아름답게 쓰일 수 있다.

단독 형태의 (산술) 연산자는 결과만 가지고 값을 반환하기 때문에 임시 객체가 만들어진다.
(operator + 나 operator - 같은...)
그리고 대입 형태 연산자는 lhs에 값을 기록하고 반환하기 때문에 임시 객체를 만들 필요가 없다.
(operator += 나 operator -= 같은...)

int a, b, c, d, result;
result = a + b + c + d; 

보다는

result = a;
result += b;
result += c;
result += d; 가 더 효율 측면에서만 보면 낫다는 것이다.

물론 위의 예는 극단적인 것이다. 

평소에 효율을 위해 저렇게 코딩하지는 않을테니.말이 그렇다는 것이다. 
하지만, operator + 를 oerator += 를 이용하여 다음과 같이 구현한다면...

  1. template<typename T>
  2. opeator + (const T& lhs, const T& rhs)
  3. {
  4.         return T(lhs) += rhs;
  5. }

대입 형태 연산자의 특성과 RVO의 특성이 합쳐져서 임시 객체 생성이 전혀 이루어지지 않게 된다.
(아~ 물론 lhs와 rhs는 서로 대입이 가능한 pair 여야만 한다)


2. NRVO (Named RVO)

vs2005부터 지원되는 NRVO는 말 그대로 이름이 있는 변수에 대해서도 RVO가 적용되는 것이다.
RVO와는 다르게 NRVO는 최적화 옵션 /O1(크기 최소화)부터 동작함을 기억하자.

최적화 모드에 따라 달라짐 Debug , Release 모드 기본 최적화 옵션이 다른데 각각 모드를 변경해보면 차이를 알 수 있음
vs2017 기준 
debug : 사용 안 함(/Od)
Release : 최대 최적화(속도 우선)(/O2)


바로 예제부터 살펴보자.

  1. class RVOSample
  2. {
  3. public:
  4.     RVOSample();
  5.     ~RVOSample();
  6.  
  7.     int value;
  8. };
  9.  
  10. RVOSample TestNRVO(int num)
  11. {
  12.     RVOSample rvo;
  13.     rvo.value = num;
  14.     return rvo;    // 임시 객체가 생성되지 않는다.
  15. }

RVO와 다르게 변수를 선언하고, 해당 변수를 사용한 다음 반환해도 임시 객체가 생성되지 않는다.

RVO에 비해 훨씬 쓰게 편하고 보기도 익숙하다.
하지만, 아래 예제와 같은 경우엔 NRVO로 최적화되지 않는다.

  1. RVOSample TestNRVO(int num)
  2. {
  3.     RVOSample rvo;
  4.     rvo.value = num;
  5.  
  6.     /- 조건에 의해 반환값이 다른 경우 NRVO로 최적화되지 않는다. *-
  7.     if (5 == num)
  8.     {
  9.         rvo.value *= 2;
  10.         return rvo;
  11.     }
  12.  
  13.     return rvo;
  14. }

이 점만 유의하면, 중간에 어떠한 코드들이 들어가도 임시 객체는 생성되지 않는다.


3. RVO / NRVO 종합 예제

  1. #include "stdafx.h"
  2. #include <iostream>
  3.  
  4. class RVOTest
  5. {
  6. public:
  7.     RVOTest() { std::cout << "Constructor\n"; }
  8.     RVOTest(const RVOTest& rhs) { std::cout << "Copy Constructor\n"; }
  9.     RVOTest(RVOTest&& rhs) { std::cout << "Move Constructor\n"; }
  10.  
  11.     ~RVOTest() { std::cout << "Destructor\n"; }
  12. };
  13.  
  14. RVOTest RVOFunc()
  15. {
  16.     return RVOTest();
  17. }
  18.  
  19. RVOTest NRVOFunc()
  20. {
  21.     RVOTest r;
  22.     return r;
  23. }
  24.  
  25. int _tmain(int argc, _TCHAR* argv[])
  26. {
  27.     /*
  28.         Constructor
  29.         Destructor
  30.     */
  31.     {
  32.         RVOTest r1 = RVOFunc();                
  33.     }
  34.  
  35.     std::cout << "=============================\n";
  36.  
  37.    //이건 최적화 모드에 따라 달라짐 Debug , Release 모드 기본 최적화 옵션이 다른데 각각 모드를 변경해보면 차이를 알 수 있음
  38.     /*
  39.         Constructor
  40.         Destructor
  41.     */
  42.     {
  43.         RVOTest r2 = NRVOFunc();
  44.     }
  45.  
  46.     return 0;
  47. }




ref : http://egloos.zum.com/himskim/v/3630181

ref : http://egloos.zum.com/sweeper/v/1942099





반응형
반응형

우리는 전역(global) 변수와 정적(static) 변수를 혼용해서 쓰는 경우가 많다.

어느 정도 차이가 있다는것은 알지만 그 디테일에 대해서는 잘 모르는데,

이번 포스팅에서는 각 변수의 디테일한 특징과 차이에 대해 설명해 보도록 하자.







1. 전역 (global) 변수


라이프 타임은 프로그램이 죽을때까지이며, 다른 파일에서도 해당 변수에 접근이 가능하다.

scope가 없는 곳에서 "int g_value" 이런 식으로 선언한 변수는 전역 변수가 된다.

막상 쓰려고 할때 자주 헷갈리는 부분인데 이 변수를 다른 파일에서 사용하기 위해서는

"extern int g_value" 이런식으로 선언하고 사용해야한다. 이말은 int g_value가 다른 파일 어딘가에 있다는 것을 나타낸다.

정리하자면 본문에서는 "int g_value", 다른 파일에서는 "extern int g_value"이런 식으로 선언하여 사용해야 한다는것.








2. 정적 (static) 변수


라이프 타임은 전역변수와 마찬가지로 프로그램이 죽을때까지이다.

static이라는 키워드가 반드시 붙으며, 해당 변수가 선언된 scope에 따라서 접근 가능한 범위가 결정된다.

"static int s_value"

위와 같이 선언되며 다른 파일에서는 접근 할 수 없으며 오직 해당 파일에서만 scope에 맞게 접근이 가능하다.


흔히 비지역 정적 객체, 지역 정적 객체라는 헷갈리는 말을 많이 쓰는데, 이 말만 보면 비지역 정적 객체는 scope 없는 곳에서 만든 static int s_value같은 느낌이지만 그냥 지역적으로 만들어 지지 않은 전역 변수 + 정적 변수를 통칭한다. (Effective C++ 번역의 폐혜인듯..)








3. 전역 변수와 정적 변수의 메모리 할당 위치




위 메모리 맵을 보면 위쪽에 .data영역과 .bss영역이 있다.


.bss 영역은 초기화 되지 않은 전역변수 및 정적변수가 존재하는 공간으로 모든 값은 0으로 초기화되며

.data 영역은 초기화 된 전역변수 및 정적변수가 존재하는 공간이다. 하지만 수식이나 함수에 의해 초기화 된 변수일 경우, .data가 아닌 .bss 영역으로 들어가게 된다.



다음 각각의 경우를 통해 실제로 어떻게 메모리에 올라가는지 살펴보자



해당 코드를 컴파일하고, 어셈블리 코드를 까보면 다음과 같이 나오는걸 볼 수 있다.


보는것 처럼 초기화를 한 변수는 _DATA 영역에 메모리가 잡히며, 초기화 하지 않은 변수는 _BSS영역에 잡히는 것을 볼 수 있다.

하지만 static 변수의 경우, 그냥 global 변수와는 다르게 초기화를 하지 않은 상태에서 해당 변수를 본문 어디에서도 사용하지 않으면 메모리 자체가 잡히지 않는걸 확인 할 수 있었다.

반면 global 변수는 초기화를 하지 않더라도 무조건 메모리에 올라간다.

참고로 위 예제에서는 s_value2 변수를 본문에서 사용하고 있기때문에 메모리에 올라간 것이다.









4. 전역 변수와 정적 변수의 초기화 시점




① 정적 '객체'의 초기화

위 코드를 보면 static 변수와 static 객체를 지역적으로 선언하고 초기화 하고 있다.

그렇다면 초기화를 했기 때문에 두 값다 _DATA영역에 올라갈 것이고, 값의 초기화는 메모리에 올라감과 동시에 진행될 것인가?




어셈파일을 까본 결과 일반 변수의 대입 초기화 같은 경우 예상대로 _DATA영역에 올라가고 값도 초기화 되지만,

클래스 객체의 경우 _BSS영역에 올라가게 된다.

이렇게 _BSS영역에 올라간 객체는 대입이 이라면 dynamic initializer가 진행되고, 생성자를 통한 초기화라면 해당 변수가 처음 수행되는 시점에 초기화가 이루어진다. (dynamic initializer에 대해선 뒤에서..)





객체의 초기화를 디스어셈블로 지켜보면, 처음 해당 변수의 선언부가 수행되는 시점에 eax 레지스터의 값을 플레그로하여 값이 0이면 생성자 구문을 수행하고, 1이면 해당 생성자 코드는 점프해 버린다.


우리가 프로파일러를 만들때 static 객체를 이용하는 경우가 있는데, 저런식으로 scope안에 객체를 만들고 생성자를 통해 초기화를 진행하면 메모리는 처음부터 올라가 있지만 해당 생성자 구문은 처음 수행되는 시점에 한 번만 호출되는 것이다.

(루프문을 다시 돌아와 static Test class_value(10); 구문을 지나도 수행하지 않고 점프해 버린다는 뜻이다)





② dynamic initializer (동적 초기화)

그렇다면 위에서 말한 dynamic initializer란 무엇일까?

몇가지 예제 코드를 보자.


위 코드의 경우 컴파일 시점에서 functionValue라는 변수의 값을 알아 낼 수 없기 때문에 해당 변수는 _BSS영역에 메모리만 올리고(0으로도 초기화 될것이다) 추후에 동적으로 초기화가 수행된다.

main()함수가 실행되기 전에 해당 변수는 dynamic initializer를 수행하게 되고,

main 구문을 수행하고 있을때는 이미 10이란 값으로 초기화가 되어 있을것이다.









또다른 예를 보자.


다른 파일에 global 변수를 선언한다.



main 파일에서는 해당 변수를 extern으로 얻어다가,

static int s_value에 더한다. 이때 s_value값은 무엇으로 채워질것인가?



위 함수 예제와 마찬가지로 _BSS영역에 먼저 메모리를 올려 놓고 dynamic initializer를 수행한다.

다른 파일에 있는 global변수를 extern으로 선언하여 가져다가 사용할 때, 해당 변수의 값을 알수 있는 시점은 컴파일 시점이 아닌 링크 시점이다.


이를 번역단위라고 하는데, 각 파일은 각각의 번역단위라 할 수 있고 이 번역단위들은 링크시점에 하나의 실행파일로 묶이게 된다. 즉, 컴파일 시점에 알 수 없는 값이므로 임시로 메모리만 올려 놓고 dynamic initializer를 통해 main() 함수 전에 수행되는것이다. 


이와 관련해서 Effective C++에서는 다른 번역단위에서 사용하는 global 변수들의 초기화 순서가 정해져 있지 않음을 지적하고 있다.

번역단위가 여러개인 시점에서 위의 g_value1을 사용하는 다른 파일이 또 있고, g_value가 아직 링크되기 전이라면 0값으로 쓰여져 어떤 파일에서 g_value1 변수를 사용하는 부분이 있다면 문제의 소지가 생긴다는 뜻이다.


그래서 저자는 싱글톤처럼 초기화 시점을 조정할 수 있도록 만들기를 권고한다.(디테일은 생략)








5. 전역 변수와 정적 변수의 차이 정리


① 전역(global) 변수는 다른 파일에서도 가져다 쓸수 있지만 정적(static) 변수는 해당 파일의 scope안에서만 접근이 가능하다.


② 초기화 하지 않은 정적(static) 변수의 경우 본문에서 사용하지 않으면 아예 메모리 상에 올라오지 않는다.


③ 정적(static) 객체의 경우 처음 구문이 수행되는 시점에 처음 생성자를 호출하도록 할 수 있다.. 이를 함수화하여 호출을하면 생성자의 호출 시점을 조정하는게 가능해진다.




[참고]

https://www.tumblr.com/search/%EC%A0%95%EC%A0%81    // Effective C++ 잘 해설.

https://kldp.org/files/static____________160.htm               // static에 대한 설명

http://pangate.com/541                                       // extern과 static

https://kldp.org/node/122255                                 // bss의 용도

http://stackoverflow.com/questions/5945897/what-is-dynamic-intialization-of-object-in-c // 스택 오버플로우, 동적 초기화



ref : http://chfhrqnfrhc.tistory.com/entry/전역변수와-전적변수

반응형
반응형


레지스터에서 eax 가 존재하고 물리적으로는 edx 가 일자로 위 그림처럼 위치해 있는것

eax 에서 연산하다가 넘치면 edx 로 넘친다



여기서 우선 중요한 레지스터는 

EAX, EDX, ECX, EBX, ESI, EDI 이고 이것들은 연산을 하기위한 레지스터들과 

데이터 조작을 할때 ESI, EDI 등을 사용한다 (Source, Destination)

EBP, ESP 는 스택포인터들을 말한다





어셈 명령어중


PUSHAD  : Push All General-Purpose Registers

POPAD    : Pop All General-Purpose Registers

가 있는데 pushad 는 레지스터를 빽업(스택에 저장) 하는것이고 popad 는 빽업했던 레지스터 내용들을 다시 복구하는 작업을 한다



int a=10,b=10, c=0;

__asm

{

  pushad

   mov eax,, a

   mov ebx,, b

   add eax, ebx

   mov c, eax

  popad

}



ㅇㅀ

  1. 사용하던 레지스터 값들을 스택에 빽업

  2. 위의 어셈블리에서연산 처리과정
    LSU 를 통해서 Memory Data 에서 변수 값을 읽어와 eax, edx  레지스터에 값을 복사

  3.  명령에셋 Instruction fetch 에서 더하기 명령어를 가져와서 ALU 에 넣는다 

  4. eax와 edx에 있는 값과 명령에셋에서 가져온 더하기 명령어와 함께 ALU 를 통해서 연산한다

  5. 더하기 연산을 한다음 eax 에 누적한다

  6. LSU 를 통해서 결과 값을 변수 c(메모리 영역에 있는 변수) 에 대입

  7. 이전 레지스터 값 복구





반응형
반응형

__declspec(align(#))를 사용하여 사용자 정의 데이터(예: 함수의 정적 할당 또는 자동 데이터)를 정확하게 제어합니다.

__declspec( align( # ) ) declarator  

최신 프로세서 명령을 사용하는 응용 프로그램을 작성할 경우 몇 가지 새로운 제약 조건과 문제가 생깁니다. 특히 새로운 명령을 사용할 때 데이터를 16바이트 경계에 맞춰야 하는 경우가 많습니다. 또한 자주 사용하는 데이터를 특정 프로세서의 캐시 줄 크기에 맞춤으로써 캐시 성능이 향상됩니다. 예를 들어 크기가 32바이트 미만인 구조체를 정의할 경우 해당 구조체 형식의 개체가 효과적으로 캐시되도록 이 구조체를 32바이트에 맞출 수 있습니다.

#은 맞춤 값입니다. 유효한 항목은 2, 4, 8, 16, 32 또는 64와 같이 1에서 8192(바이트) 사이에 속하는 2의 정수 제곱입니다. declarator는 aligned로 선언하는 데이터입니다.

형식의 맞춤 요구 사항인 size_t 형식의 값을 반환하는 방법에 대한 자세한 내용은 __alignof를 참조하고 64비트 프로세서를 대상으로 하는 경우 정렬되지 않은 포인터를 선언하는 방법은 __unaligned를 참조하세요.

__declspec(align(#))struct 또는 union를 정의하거나 변수를 선언할 때 class를 사용할 수 있습니다.

컴파일러는 복사 또는 데이터 변환 작업 중에 데이터의 맞춤 특성 보존을 보장하거나 시도하지 않습니다. 예를 들어 memcpy는 __declspec(align(#))로 선언된 구조체를 임의의 위치에 복사할 수 있습니다. malloc, C++ operator new 및 Win32 할당자와 같은 일반 할당자가 반환하는 메모리는 대개 __declspec(align(#)) 구조체 또는 구조체 배열에 사용할 수 있도록 충분히 맞춰지지 않은 상태입니다. 복사 또는 데이터 변환 작업의 대상이 올바르게 맞춰지도록 하려면 _aligned_malloc를 사용하거나 할당자를 직접 작성합니다.

함수 매개 변수의 맞춤을 지정할 수 없습니다. 맞춤 특성을 포함하는 데이터가 스택의 값에 의해 전달되면 해당 맞춤은 호출 규칙을 통해 제어됩니다. 호출된 함수에서 데이터 맞춤이 중요한 경우에는 사용 전에 매개 변수를 올바르게 맞춰진 메모리로 복사합니다.

__declspec(align(#))를 사용하지 않는 경우 Visual C++는 일반적으로 대상 프로세서와 데이터 크기를 기반으로 자연 경계에 데이터를 맞춥니다(32비트 프로세서에서는 최대 4바이트 경계, 64비트 프로세서에서는 최대 8바이트 경계). 클래스 또는 구조체의 데이터는 최소한의 자연 맞춤 및 현재의 압축 설정(#pragma pack 또는 /Zp 컴파일러 옵션에서)으로 클래스나 구조체에 맞춰집니다.

이 예제에서는 __declspec(align(#))의 사용을 보여 줍니다.

__declspec(align(32)) struct Str1{  
   int a, b, c, d, e;  
};  

현재 이 형식에는 32비트 맞춤 특성이 포함되어 있습니다. 즉, 모든 정적 및 자동 인스턴스가 32바이트 경계에서 시작됩니다. 이 형식을 사용하여 멤버로 선언된 추가 구조체 형식은 이 형식의 맞춤 특성을 유지하여 Str1이 요소로 지정된 모든 구조체는 32 이상의 맞춤 특성을 갖게 됩니다.

sizeof(struct Str1)는 32입니다. 즉, Str1 개체 배열을 만드는 경우 배열의 기준을 32바이트로 맞추면 배열의 각 멤버도 32바이트로 맞춰집니다. 동적 메모리에서 기준이 올바르게 맞춰진 배열을 만들려면 _aligned_malloc를 사용하거나 할당자를 직접 작성합니다.

구조체의 sizeof 값은 최종 멤버의 오프셋에 해당 멤버의 크기를 더하여 최대 멤버 맞춤 값 또는 전체 구조체 맞춤 값 중 더 큰 값의 가장 근사한 배수로 반올림한 값입니다.

컴파일러는 구조체 맞춤에 다음과 같은 규칙을 사용합니다.

  • __declspec(align(#))로 재정의하지 않으면 스칼라 구조체 멤버의 맞춤은 최소 크기와 현재 압축입니다.

  • __declspec(align(#))로 재정의하지 않으면 구조체의 멤버는 멤버의 최대 개별 맞춤입니다.

  • 구조체 멤버는 이전 멤버 끝의 오프셋보다 크거나 같은 맞춤의 최소 배수인 부모 구조체의 시작 부분부터 오프셋에 배치됩니다.

  • 구조체의 크기는 마지막 멤버의 오프셋 끝보다 크거나 같은 맞춤의 최소 배수입니다.

__declspec(align(#))는 맞춤 제한만 늘릴 수 있습니다.




맞춤 (C++)

Visual Studio 2015
 

Visual Studio 2017 에 대한 최신 설명서는 Visual Studio 2017 설명서를 참조하세요.

Visual Studio 2015 이상 버전에서는 C++11 표준 alignas 지정자를 사용하여 맞춤을 제어합니다. 자세한 내용은 맞춤을 참조하세요.

Microsoft 전용

__declspec(align(#))를 사용하여 사용자 정의 데이터(예: 함수의 정적 할당 또는 자동 데이터)를 정확하게 제어합니다.

__declspec( align( # ) ) declarator  

최신 프로세서 명령을 사용하는 응용 프로그램을 작성할 경우 몇 가지 새로운 제약 조건과 문제가 생깁니다. 특히 새로운 명령을 사용할 때 데이터를 16바이트 경계에 맞춰야 하는 경우가 많습니다. 또한 자주 사용하는 데이터를 특정 프로세서의 캐시 줄 크기에 맞춤으로써 캐시 성능이 향상됩니다. 예를 들어 크기가 32바이트 미만인 구조체를 정의할 경우 해당 구조체 형식의 개체가 효과적으로 캐시되도록 이 구조체를 32바이트에 맞출 수 있습니다.

#은 맞춤 값입니다. 유효한 항목은 2, 4, 8, 16, 32 또는 64와 같이 1에서 8192(바이트) 사이에 속하는 2의 정수 제곱입니다. declarator는 aligned로 선언하는 데이터입니다.

형식의 맞춤 요구 사항인 size_t 형식의 값을 반환하는 방법에 대한 자세한 내용은 __alignof를 참조하고 64비트 프로세서를 대상으로 하는 경우 정렬되지 않은 포인터를 선언하는 방법은 __unaligned를 참조하세요.

__declspec(align(#))struct 또는 union를 정의하거나 변수를 선언할 때 class를 사용할 수 있습니다.

컴파일러는 복사 또는 데이터 변환 작업 중에 데이터의 맞춤 특성 보존을 보장하거나 시도하지 않습니다. 예를 들어 memcpy는 __declspec(align(#))로 선언된 구조체를 임의의 위치에 복사할 수 있습니다. malloc, C++ operator new 및 Win32 할당자와 같은 일반 할당자가 반환하는 메모리는 대개 __declspec(align(#)) 구조체 또는 구조체 배열에 사용할 수 있도록 충분히 맞춰지지 않은 상태입니다. 복사 또는 데이터 변환 작업의 대상이 올바르게 맞춰지도록 하려면 _aligned_malloc를 사용하거나 할당자를 직접 작성합니다.

함수 매개 변수의 맞춤을 지정할 수 없습니다. 맞춤 특성을 포함하는 데이터가 스택의 값에 의해 전달되면 해당 맞춤은 호출 규칙을 통해 제어됩니다. 호출된 함수에서 데이터 맞춤이 중요한 경우에는 사용 전에 매개 변수를 올바르게 맞춰진 메모리로 복사합니다.

__declspec(align(#))를 사용하지 않는 경우 Visual C++는 일반적으로 대상 프로세서와 데이터 크기를 기반으로 자연 경계에 데이터를 맞춥니다(32비트 프로세서에서는 최대 4바이트 경계, 64비트 프로세서에서는 최대 8바이트 경계). 클래스 또는 구조체의 데이터는 최소한의 자연 맞춤 및 현재의 압축 설정(#pragma pack 또는 /Zp 컴파일러 옵션에서)으로 클래스나 구조체에 맞춰집니다.

이 예제에서는 __declspec(align(#))의 사용을 보여 줍니다.

__declspec(align(32)) struct Str1{  
   int a, b, c, d, e;  
};  

현재 이 형식에는 32비트 맞춤 특성이 포함되어 있습니다. 즉, 모든 정적 및 자동 인스턴스가 32바이트 경계에서 시작됩니다. 이 형식을 사용하여 멤버로 선언된 추가 구조체 형식은 이 형식의 맞춤 특성을 유지하여 Str1이 요소로 지정된 모든 구조체는 32 이상의 맞춤 특성을 갖게 됩니다.

sizeof(struct Str1)는 32입니다. 즉, Str1 개체 배열을 만드는 경우 배열의 기준을 32바이트로 맞추면 배열의 각 멤버도 32바이트로 맞춰집니다. 동적 메모리에서 기준이 올바르게 맞춰진 배열을 만들려면 _aligned_malloc를 사용하거나 할당자를 직접 작성합니다.

구조체의 sizeof 값은 최종 멤버의 오프셋에 해당 멤버의 크기를 더하여 최대 멤버 맞춤 값 또는 전체 구조체 맞춤 값 중 더 큰 값의 가장 근사한 배수로 반올림한 값입니다.

컴파일러는 구조체 맞춤에 다음과 같은 규칙을 사용합니다.

  • __declspec(align(#))로 재정의하지 않으면 스칼라 구조체 멤버의 맞춤은 최소 크기와 현재 압축입니다.

  • __declspec(align(#))로 재정의하지 않으면 구조체의 멤버는 멤버의 최대 개별 맞춤입니다.

  • 구조체 멤버는 이전 멤버 끝의 오프셋보다 크거나 같은 맞춤의 최소 배수인 부모 구조체의 시작 부분부터 오프셋에 배치됩니다.

  • 구조체의 크기는 마지막 멤버의 오프셋 끝보다 크거나 같은 맞춤의 최소 배수입니다.

__declspec(align(#))는 맞춤 제한만 늘릴 수 있습니다.

자세한 내용은 다음을 참조하세요.

다음 예제에서는 __declspec(align(#))가 데이터 구조체의 크기 및 맞춤에 영향을 주는 방식을 보여 줍니다. 예제에서는 다음과 같은 정의를 가정합니다

#define CACHE_LINE  32  
#define CACHE_ALIGN __declspec(align(CACHE_LINE))  



이 예제에서는 S1를 사용하여 __declspec(align(32)) 구조체를 정의합니다. 변수 정의에 대해 또는 기타 형식 선언에서 사용되는 모든 S1은 32바이트로 맞춰집니다. sizeof(struct S1)는 32를 반환하고 S1은 16바이트 뒤에 정수 4개에 필요한 16 패딩 바이트를 둡니다. 각 int 멤버는 4바이트로 맞춰야 하지만 구조체 자체의 맞춤은 32로 선언됩니다. 따라서 전체 맞춤은 32입니다.

struct CACHE_ALIGN S1 { // cache align all instances of S1  
   int a, b, c, d;  
};  
struct S1 s1;   // s1 is 32-byte cache aligned  



이 예제에서는 최대 맞춤 요구 사항의 배수가 8의 배수인 16이므로 sizeof(struct S2)는 멤버 크기의 합계와 똑같은 16을 반환합니다.

__declspec(align(8)) struct S2 {  
   int a, b, c, d;  
};  



다음 예제에서 sizeof(struct S3)는 64를 반환합니다.

struct S3 {  
   struct S1 s1;   // S3 inherits cache alignment requirement  
                  // from S1 declaration  
   int a;         // a is now cache aligned because of s1  
                  // 28 bytes of trailing padding  
};  



이 예제에서 a에는 자연 형식 맞춤(여기서는 4바이트)이 사용됩니다. 그러나 S1은 32바이트 맞춤이어야 합니다. 28바이트 패딩이 a 뒤에 나와 s1이 32 오프셋에서 시작됩니다. 그런 다음 S4가 구조체의 최대 맞춤 요구 사항인 S1의 맞춤 요구 사항을 상속합니다. sizeof(struct S4)가 64를 반환합니다.

struct S4 {  
   int a;  
   // 28 bytes padding  
    struct S1 s1;      // S4 inherits cache alignment requirement of S1  
};  








다음 3개의 변수 선언에도 __declspec(align(#))가 사용됩니다. 각 선언에서 변수가 32바이트 맞춤이어야 합니다. 배열의 경우 각 배열 멤버가 아니라 배열의 기준 주소가 32바이트 맞춤입니다. sizeof를 사용해도 각 배열 멤버의 __declspec(align(#)) 값에 영향을 주지 않습니다.

CACHE_ALIGN int i; CACHE_ALIGN int array[128]; //배열의 경우에 32 바이트단위로 맞춤(끊어)정렬된다 CACHE_ALIGN struct s2 s;




배열의 각 멤버를 맞추려면 다음과 같은 코드를 사용해야 합니다.

typedef CACHE_ALIGN struct { int a; } S5;  
S5 array[10];  


반응형
반응형

what code is it?


#define FORCENOINLINE __attribute__((noinline)) /* Force code to NOT be inline */



In brief, this code not use inline in c++ code and UE4 use it as FORCENOINLINE  macro,



FORCENOINLINE construct()  , you know what it means?, that's right





How can I tell gcc not to inline a function?



Say I have this small function in a source file

static void foo() {}

and I build an optimized version of my binary yet I don't want this function inlined (for optimization purposes). is there a macro I can add in a source code to prevent the inlining?

shareimprove this question
   
Thanks for this question! I was profiling with oprofile when a function did not show up, the answers here fixed this. – Simon A. Eugster Oct 29 '11 at 9:17

You want the gcc-specific noinline attribute.

This function attribute prevents a function from being considered for inlining. If the function does not have side-effects, there are optimizations other than inlining that causes function calls to be optimized away, although the function call is live. To keep such calls from being optimized away, put asm ("");

Use it like this:

void __attribute__ ((noinline)) foo() 
{
  ...
}
shareimprove this answer
25 
Using gcc 4.4.3 on Arch Linux, I get a syntax error with the attribute placed as above. It works correctly when it precedes the function (e.g., attribute ((noinline)) void foo() {}) – mrkj Apr 16 '10 at 14:24
2 
Arduino also wanted it placed before the function. – Peter N Lewis Feb 24 '12 at 9:49
2 
Edited to fix the attribute syntax. – Quuxplusone Jun 21 '13 at 20:59
   
The asm("") construct is actually fairly cross-platform and got the job done. I did it for x86 Linux and it did not cause a build problem on PowerPC AIX. Thanks for this useful suggestion! – Marty Nov 6 '14 at 23:58
   
Using gcc 6.2.1 on Arch Linux, no problem. – ManuelSchneid3r Oct 1 '16 at 14:46 






반응형
반응형

자식 클래스에서 특별히 필요하지 않으면 복사 생성자를 생성하지 않고 디폴트 복사 생성자를 이용할 수 있다. 컴파일러는 복사 생성자가 정의되지 않으면 알아서 디폴트 복사 생성자를 생성한다. 그러나 깊은 복사를 위해 직접적으로 복사 생성자를 정의한다고 할 때, 자식 클래스에서 부모 클래스의 복사 생성자를 명시적으로 호출해주지 않을 경우 부모 클래스의 멤버에 대해 복사 생성자가 호출되지 않고 디폴트 생성자가 호출되는데 그친다.


#include <iostream>
 
using namespace std;
 
class Parent
{
private:
    int x;
 
public:
    Parent(int n = 0) : x(n) {}
    Parent(const Parent& copy) : x(copy.x) {}
 
    int getX() { return x; }
};
 
class Child : public Parent
{
private:
    int y;
 
public:
    Child(int n1 = 0, int n2 = 0) : Parent(n1), y(n2) {}
    Child(const Child& copy) : y(copy.y) {}
 
    int getY() { return y; }
};
 
int main(void)
{
    Child c1(1);
    Child c2(c1);
     
    cout << c1.getX() << ',' << c1.getY() << endl;
    cout << c2.getX() << ',' << c2.getY() << endl;
 
    return 0;
}



1,0

0,0


 부모 클래스의 멤버에 대해 정상적으로 복사 생성자가 호출되기 위해 자식 클래스의 복사 생성자에서 명시적으로 부모 클래스의 복사 생성자를 호출해주자.


1
Child(const Child& copy) : Parent(copy), y(copy.y) {}

1,0

1,0


출처 http://wonthing86.tistory.com/59

반응형
반응형


 

 클로져 객체의 복사 생성자와 소멸자

 




모든 클로져 객체들은 암묵적으로 정의된 복사 생성자(copy constructor)와 소멸자(destructor)를 가지고 있습니다. 이 때 클로져 객체가 복사 생성 될 때 값으로 Capture 된 것들의 복사 생성이 일어나겠지요. 아래의 예를 한번 보도록 하겠습니다.


일단 


struct trace

{

trace() : i(0) { cout << "construct\n"; }

trace(trace const &) { cout << "copy construct\n"; }

~trace() { cout << "destroy\n"; }

trace& operator=(trace&) { cout << "assign\n"; return *this;}

int i;

};


와 같이 생성, 복사 생성, 소멸, 그리고 대입 연산을 확인할 수 있는 trace 라는 구조체를 정의해놓고 


trace t;

int i = 8;


auto f = [=]() { return i / 2; };


를 한다면 어떻게 나올까요? f 에서 t 를 사용하지 않았으므로, t 를 Capture 하지 않게 됩니다. 따라서 그냥



이 나오게 됩니다.


그렇다면 아래의 예는 어떨까요


trace t;

int i = 8;


auto m1 = [=]() { int i = t.i; };

cout << " --- make copy --- " << endl;


auto m2 = m1;


먼저 m1 을 생성하면서, 람다가 t 를 Capture 하였으므로 t 의 복사 생성자가 호출되게 됩니다. 왜냐하면 값으로 받았기 때문이지요. 만일 레퍼런스로 받았다면 복사 생성자가 호출되지 않았을 것입니다 (확인해보세요!) 그리고 아래의 auto m2 = m1; 에서 클로져 객체의 복사 생성이 일어나는데, 이 때, 클로져 객체의 복사 생성자가 값으로 Capture 된 객체들을 똑같이 복사 생성 해주게 됩니다. 따라서 또 한번 t 의 복사 생성자가 호출되겠지요. 그 결과 아래와 같이 출력됩니다.






ref : http://itguru.tistory.com/196 

반응형
반응형

Circular references


http://sweeper.egloos.com/viewer/2826435

http://goo.gl/B1mEP9\


 

9) Circular references

레퍼런스 카운팅 기반이기에 순환 참조에 대한 잠재적인 문제가 있을 수 있다.
즉, A와 B가 서로에 대한 shared_ptr을 들고 있으면 레퍼런스 카운트가 0이 되지 않아 메모리가 해제되지 않는다.

게임의 경우를 예로 들어, 파티 객체가 있으면 파티원 객체가 있을 것이다.
파티 객체는 파티원 객체를 리스트 형태로 들고 있고, 또 파티원도 자기가 속한 파티 객체를 알기 위해 참조 형태로 들고 있는 예제를 살펴보자.

  1. #include <memory>    // for shared_ptr
  2. #include <vector>
  3.  
  4. using namespace std;
  5.  
  6. class User;
  7. typedef shared_ptr<User> UserPtr;
  8.  
  9. class Party
  10. {
  11. public:
  12.     Party() {}
  13.     ~Party() { m_MemberList.clear(); }
  14.  
  15. public:
  16.     void AddMember(const UserPtr& member)
  17.     {
  18.         m_MemberList.push_back(member);
  19.     }
  20.  
  21. private:
  22.     typedef vector<UserPtr> MemberList;
  23.     MemberList m_MemberList;
  24. };
  25. typedef shared_ptr<Party> PartyPtr;
  26.  
  27. class User
  28. {
  29. public:
  30.     void SetParty(const PartyPtr& party)
  31.     {
  32.         m_Party = party;
  33.     }
  34.  
  35. private:
  36.     PartyPtr m_Party;
  37. };
  38.  
  39.  
  40. int _tmain(int argc, _TCHAR* argv[])
  41. {
  42.     PartyPtr party(new Party);
  43.  
  44.     for (int i = 0; i < 5; i++)
  45.     {
  46.         // 이 user는 이 스코프 안에서 소멸되지만,
  47.         // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1
  48.         UserPtr user(new User);
  49.  
  50.         // 아래 과정에서 순환 참조가 발생한다.
  51.         party->AddMember(user);
  52.         user->SetParty(party);
  53.     }
  54.  
  55.     // 여기에서 party.reset을 수행해 보지만,
  56.     // 5명의 파티원이 party 객체를 물고 있어 아직 refCount = 5 의 상태
  57.     // 따라서, party가 소멸되지 못하고, party의 vector에 저장된 user 객체들 역시 소멸되지 못한다.
  58.     party.reset();
  59.  
  60.     return 0;
  61. }

위와 같은 형태로 shared_ptr이 서로를 참조하고 있는 것은 circular reference라고 한다.

위 예제처럼, 그룹 객체 - 소속 객체간 상호 참조는 실무에서도 흔히 볼 수 있는 패턴이며, 
보통은 위 예제처럼 직접 참조 형식이 아니라, User는 PartyID를 들고 있고, Party 객체에 접근 필요시 PartyManger(컬렉션)에 질의해서 유효한 Party 객체 포인터를 얻어오는 방식을 사용한다.

그렇다고, PartyManager에 일일히 ID로 검색하는 비용을 줄이고자, Party 포인터를 직접 들고 있으면, 들고 있던 포인터가 dangling pointer가 될 수 있는 위험이 있다.

이럴 때, User 객체가 Party 객체를 shared_ptr가 아닌 weak_ptr을 사용하여 들고 있다면, 검색 비용 회피와 dangling pointer의 위험에서 모두 벗어날 수 있다.

std::weak_ptr은 shared_ptr로부터 생성될 수 있고, shared_ptr이 가리키고 있는 객체를 똑같이 참조하지만, 참조만 할 뿐 reference counting은 하지 않아 위 예제의 목적에 가장 바람직한 대안이 될 수 있다 할 수 있다.






반응형
반응형
가변 템플릿은 c 에서 가변인자 함수에서

#include <iostream>
using namespace std;
template <typename T>
void print(const T& t)
{
cout << t << endl;
}
template <typename First, typename... Rest>
void print(const First& first, const Rest&... rest)
{
cout << first << ", ";
print(rest...); // recursive call using pack expansion syntax
}
int main()
{
print(100, 200, 201, 202, 300);
print("first", 2, "third", 3.14159);
}










//실행 결과

100, 200, 201, 202, 300

first, 2, third, 3.14159


참고 : https://msdn.microsoft.com/ko-kr/library/dn439779(v=vs.140).aspx




http://egloos.zum.com/Lusain/v/3158201



C에서는 va_list 사용하여 가변인자를 처리해야 했다.

 

va_start 시작해서 va_end 끝내야 했으며어떤 타입인지 미리 알려주어야 하거나 모든 같은 형으로 가변인자를 사용해야 했다.불편했고, C 마지막에 printf scanf 동작원리로 배운 뒤로는   적이 없던 개념이다.

 

C++ 와서 template 이라는 개념이 생겼다어떤 형태라도 template으로 뭉뚱그려 인자로 받을  있게  것이다그리고 가변인자역시 template, 탬플릿으로 받을  있다.

 

가변인자 탬플릿은 다음과 같이 선언한다.

 

template<typename  첫번째_가변인자_인수>

반환형 함수명(첫번째_가변인자_인수... 가변인자_변수);

 

타입이름 앞에 ...  붙여 가변인자 탬플릿이라고 알려준다.

한글로  이름은 원하는 타입을 적으면 된다이렇게.

 

template<typename... Args>

void print(Args... args)

{

...

print(args...);

}

 

매개변수로  경우 변수명의  ... 붙여준다.

 

탬플릿 선언 : typename 뒤에 : typename... Ty

선언 : 타입의  ... : void print(Ty... ty);

사용 : 변수의  ... : print(ty...);

 

 

그런데 이렇게 쓰면 가변 인자의 내용을 확인을  하기 때문에 다음과 같이 ' 번째 가변 인자' 명시한다.

 

template<typename  첫번째_가변인자_인수typename... 가변인자_타입이름>

반환형  함수명( 첫번째_가변인자_인수 변수명가변인자_타입이름... 가변인자_변수);

 

예를 들면 이렇게 적으면 된다.

 

template<typename  Tytypename ... Args>

void print(Ty ty, Args...args)

 

'가변인자_변수'  번째 인자를 제외한 남은 가변인자를 가지고 '첫번째_가변인자_인수'  번째 인자로 들어가게 된다이걸재귀적으로 시행하는 것이 가변인자 함수의 핵심이다.

 그런데  예제에서는 Ty라는 탬플릿으로 인자를 받았기 때문에 Ty 어떤 타입인지   없다. C++ 공부한 사람들이라면  사용할 만한 좋은 개념을  것이다.

 함수 오버로딩(overloading : 매개인자에 따라 함수명이 동일한 함수들을 재정의하는 기능) 사용하는 것이다.

 

그러면 사용 예는 다음과 같아진다.

 

void printType(int i);

void printType(string s);

...

 

template<typename  Tytypename ... Args>

void print(Ty ty, Args...args)

{

printType(ty);

print(args...);

}

 

이쯤에서 의문이 하나 생긴다가변인자가 하나씩 빠진 끝에 가변인자가 하나도 남지 않을 경우에는?

 

가변인자 탬플릿을 사용할 경우위와 같은 문제를 해결하기 위해 매개인자가 없거나마지막에  매개인자를 가지는

"가변인자탬플릿 함수와 동일한 함수 이름을 가지는" 함수를 오버로딩 해야 한다.

  함수는 가변인자를 모두 처리한 뒤의 후처리를 위한 함수로, C에서의 va_end()  동일한 역할을 수행한다

차이점은, va_end() 없다고 컴파일 에러를 부르지 않지만 가변인자 탬플릿은원하는 형식의 함수가 오버로딩되지 않으면 컴파일 오류 뿜는다.


















반응형
반응형

블로그 이미지


operator 는 상속되지 않는다



그래서 다음과 같은 에러를 벹는다


class ttt

{

public :


void operator=(int a) {

std::cout  << "ttttt "<< a << std::endl;

}

};



class ddd : public ttt

{

public :

};



int main()

{


ddd d1;

d1.operator=(10);



오류 C2664 'ddd &ddd::operator =(ddd &&)': 인수 1을(를) 'int'에서 'const ddd &'(으)로 변환할 수 없습니









그런데 c++ 11 에서부터 생성자를 상속 받을 수 있는 상속생성자 using 을 제공하는데


이때 operator 또한 상속 받을 수 있게 지원해준다



class ttt

{

public :


void operator=(int a) {

std::cout  << "ttttt "<< a << std::endl;

}


};



class ddd : public ttt

{


public :


using ttt::operator=;

};



int main()

{


ddd d1;

d1.operator=(10);


return 0;

}


좀 더 자세한 using 은 http://3dmpengines.tistory.com/1606 상속 생성자를 참고..

반응형
반응형



상속생성자란 기본클래스에 정의된 생성자를 상속받는 클래스에서 상속받을 수 있도록 하는 기능이다.


http://yesarang.tistory.com/374

상속생성자는 컴파일러가 기본생성자와 복사생성자를 생성하는 규칙 때문에 발생하는 문제점을 해결하기 위한 기능이므로 우선 이 규칙을 이해할 필요가 있다. C++ 컴파일러는 프로그래머가 생성자를 정의하지 않으면 기본 생성자 및 복사생성자를 알아서 생성한다. 예를 들어,

class B {
 int v_;

public:
 int get();
 void set(int v);
};

B b;

와 같은 코드가 아무런 문제 없이 컴파일되는 이유는 컴파일러가 기본생성자를 생성해 주기 때문이다. 그런데 B를 쓰다 보면 기본생성자에 의해 초기화되지 않은 멤버 변수 값 때문에 문제가 생기는 경우가 있기 마련이다. 예를 들어,

void f();

B b;
f(b.get());

와 같은 코드를 실행할 경우 b.v_에 담긴 쓰레기 값 때문에 문제가 발생할 수 있을 것이다. 이런 문제를 발견했을 때 상식있는 프로그래머라면 다음과 같이 생성자를 추가할 것이다.

class B {
 int v_;

public:
 B(int v) : v_(v) {}
 int get();
 void set(int v);
};

B b;

그러면 당장 다음과 같은 에러가 발생하게 된다.

error C2512: 'B' : 사용할 수 있는 적절한 기본 생성자가 없습니다.

그렇다. 프로그래머가 기본생성자가 아닌 생성자를 하나라도 추가하면 컴파일러가 기본생성자를 생성하지 않는 것이다. 이제 프로그래머가 직접 기본생성자를 작성해야 한다.

class B {
 int v_;

public:
 B() : B(0) {} // C++11의 위임생성자 기능 활용
 B(int v) : v_(v) {}
 int get();
 void set(int v);
};

그런데 만약 B로부터 상속 받은 D라는 클래스가 있었다고 생각해 보자.

class D : public B {
public:
 void compute();
};

B::B(int)가 추가된 이유를 이해하는 프로그래머라면 D를 사용할 때 다음과 같이 생성시 초기값을 제대로 지정하려고 할 것이다.

D d(10);

그렇지만 위 코드는 컴파일 에러를 발생시킨다. 왜냐하면 D::D(int)는 애초에 정의되어 있지 않기 때문이다. 결국 D를 작성한 프로그래머는 다음과 같이 D::D(int)를 추가해야 한다.

class D : public B {
public:
 D(int v) : B(v) {}
 void compute();
};

D d(10);

그렇다면 다음 코드는 어떨까?

D d2;
d2.set(10);

D::D(int)가 없었기 때문에 충분히 여러 차례 사용될만한 코드 패턴일 것이다. 그렇지만 D를 작성한 프로그래머가 D::D(int)를 추가한 순간 위와 같은 코드 패턴들은 모두 컴파일 에러를 일으키게 된다. D 클래스를 작성한 프로그래머와 위 코드를 작성한 프로그래머가 같다면 별 문제 없겠지만 다르다면 사무실 어느 구석에선가 “악~”하고 소리가 날 일이다. 결국 D는 다음과 같이 작성되어야 한다.

class D : public B {
public:
 D() : B() {}
 D(int v) : B(v) {}
 void compute();
};

D d(10);
D d2;
d2.set(10);

창조적 귀차니즘이 있는 프로그래머라면 이쯤에서 의문이 하나 생긴다. “왜 이렇게 불편하게 해 놓았지? 애초에 DB의 생성자들을 다 물려받으면 되잖아. 상속에는 생성자 상속까지 포함되어야 하는 거 아닌가?”

상속생성자는 이런 의문에서 출발한 기능이다. 위 질문에 대한 해답으로 상속시에 생성자를 모두 물려받는 것으로 정하면 되지 않을까 하는 생각이 제일 먼저 들 것이다. 그렇지만 이렇게 상속의 의미를 수정할 경우 기존에 이미 존재하던 수많은 코드들이 컴파일되지 않거나 컴파일은 되는데 예상치 못한 실행시 에러가 발생할 수 있다. 언어가 발전함에 있어 이런 급작스런 변화는 거의 불가능하다고 볼 수 있다. 기존 코드를 모두 깨뜨려버린다면 누구도 새로운 기능들을 채택하려 하지 않을 것이기 때문이다.

그렇다면 결국 생성자를 따로 상속받을 수 있는 방법을 고안해 내야 할 것이다. C++11 표준화 논의 중에 여러가지 방법들이 제안되었지만 최종적으로는 다음과 같이 using 선언을 이용하여  생성자를 물려받을 수 있다.

class B {
 int v_;

public:
 B() : B(0) {} // C++11의 위임생성자 기능 활용
 B(int v) : v_(v) {}
 int get();
 void set(int v);
};

class D : public B {
public:
 using B::B;
 void compute();
};

위와 같이 정의할 경우, D::D(int)가 암묵적으로 정의되며, 그 의미는 다음과 동일하다고 생각하면 된다.

class D : public B {
public:
 D(int v) : B(v) {}
 ...
};

따라서 다음과 같이 D를 사용할 수 있게 된다.

D d1; // 암묵적으로 정의됨
D d2(10); // 상속을 통해 암묵적으로 정의됨
D d3(d2); // 암묵적으로 정의됨

D::D(int)가 “암묵적으로” 정의된다는 것은 기본생성자인 D::D()과 복사생성자인 D::D(const D&)가 암묵적으로 정의된다는 의미이다. 왜냐하면 D::D(int)를 프로그래머가 명시적으로 정의한 것은 아니기 때문에 기본생성자 및 복사생성자 생성규칙에 따라 암묵적으로 정의된 것이다.

이렇게 상속생성자가 동일한 signature를 갖는 생성자를 반복해서 작성해야 하는 수고로움은 덜어 줬지만 잘못하면 다른 문제를 일으킬 수도 있다. 다음 예제를 보자.

class B {
 int v_;

public:
 B() : B(0) {} // C++11의 위임생성자 기능 활용
 B(int v) : v_(v) {}
 int get();
 void set(int v);
};

class D : public B {
 float v2_; // 초기화 필요
public:
 using B::B;
 void compute();
};

D d3(25);

언뜻 보기에는 문제가 없어 보일 수 있으나 컴파일러가 생성한 D::D(int)B::B(int)를 뜻하므로 d3.v2_는 초기화되지 않은 채로 사용될 것이다. 이쯤에서 우리는 다음과 같은 프로그래밍 경구를 기억할 수 밖에 없다.

“초기화되지 않은 변수를 사용하지 말라”

구식 방법으로 D::D(int)를 직접 작성하여 초기화할 수도 있지만, C++11에 새롭게 추가된 멤버초기화 기능을 이용하여 더 멋지게 해결할 수도 있다.

class D : public B {
 float v2_{0.0};        // 멤버초기화 기능 이용
public:
 using B::B;
 void compute();
};

D d3(25);                // v2_는 0.0으로 초기화 됨

참고문헌
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1890.pdf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2540.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1898.pdf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1583.pdf
http://www2.research.att.com/~bs/C++0xFAQ.html#inheriting
http://occamsrazr.net/tt/205

반응형
반응형



기존 typedef 로는 템플릿 인자는 template 을 허용하지 못했다




template<typename T>

typedef  std::vector<T> g1;            //이거 에러, 


에러명

/*

오류 C2823 형식 정의 템플릿이(가) 잘못되었습니다. Project1

*/




//그런데 using 을 다음처럼 사용해  템플릿 인수를 받아 다시 정의하는게 가능하다


template<typename T>

using ddd111 = std::vector<T>;





int main()

{


ddd111<int> d1;

d1.push_back(2);



..

}




이렇게 쓸대는 전역 변수로 타입을 선언해야 한다



참고 :


함수 포인터 또한 템플릿 인자를 받아 다시 정의 하는게 가능하다

http://blog.naver.com/bluerein_/220563677309






http://spikez.tistory.com/271


항목 9 : typedef 보다는 별칭(using) 을 선호하라.



STL 컨테이너들을 사용하는 것이 바람직하다. 이건 아마 c++을 사용하는 사람들은 동의 하리라 생각합니다.

std::unique_ptr의 사용 역시 바람직하다는 점에 대해서도 동의 할것 같구요.(저도 동의합니다.)


하지만, "std::unique_ptr<unordered_map<std::string, std::string>>" 을 여러번 타이핑하는 것은 하고 싶지 않죠( 120% 공감)


이럴때 typedef 를 사용합니다.

typedef std::unique_ptr<unordered_map<std::string, std::string>> UPtrMapSS;


C++98 에서 사용하던 방식이죠.


C++11에서는 별칭선언(alias declaration) 을 제공합니다.


using UPtrMapSS = std::unique_ptr<unordered_map<std::string, std::string>>;



이 둘의 차이가 있을까요?


그전에 한가지!!! (사실 이것 하나만 보더라도 흥미롭습니다.)


typedef void(*FP)(int , const std::string&);


using FP = void (*)(int, const std::string&);


둘 모두 같은 의미입니다. 함수 포인터를 type으로 선언한것인데요.

눈으로 보기에는 using으로 선언한 것이 더 편하고 명확해 보입니다.(물론 함수포인터 선언에 한해서만 봤을때죠.)



본격적으로 using을 선호해야 하는 이유에 대해서 다룹니다.


첫번째 typedef는 template 화 할 수 없습니다. using은 template화 할수 있습니다.


template<typename T>

using MyAllocList = std::list<T, MyAlloc<T>>;

MyAllocList<Widget> wg;




typedef를 사용할 경우,


template<typename T>

struct MyAllocList {

  typename std::list<T, MyAlloc<T>> type;

};


MyAllocList<Widget>::type wg;



이를 활용하는 template class를 만든다고 합시다.


template <typename T>

class Widget {

 private:

   typename MyAllocList<T>::type list;

};


C++의 규칙중에는 의존적인 형식(dependent type)의 이름 앞에 반드시 typename을 붙여야 합니다.


MyAllocList<T>::type은 템플릿 형식 매개변수 T에 의존적인 형식입니다.

따라서 typename을 붙여야 하며, 또 ::type역시 꼭 필요합니다.


보다시피 typedef를 사용하였을때 만들어야 하는 코드를 보면 "어떻게든 되게 만드는" 코드 처럼 보입니다.


using은 이런 점들을 보완해서 template을 좀더 자연스럽게 사용할 수 있도록 제공합니다.

template<typename T>

using MyAllocList = std::list<T, MyAlloc<T>>;

template <typename T>

class Widget{

 private:

  MyAllocList<T> list;

}


그리고 다음 사항까지도 고려해야합니다.

이 경우 MyAllocList<T>::type은 데이타 타입이 아니라 변수 type이 됩니다.

따라서 반드시 typename을 사용해야 합니다.


class Wine {...};


template <>

class MyAllocList<Wine> {

private:

  enum class WineType {Red, White, Rose};

  WinType type;

};

템플릿 메타 프로그래밍은 결국 현재까지 개발된 코드에서 문제 없는가도 중요하지만,

앞으로 개발될 새로운 코드에서도 적용 가능한가도 매우 중요하죠..


그때문에 이런 내용들까지도 고려를 해야합니다.




여기서 잠깐, 또 한가지 주의점이 있습니다.

C++11의 STL 에 type과 관련된 template method들이 있습니다.

std::remove_const<T>::type   // const T를 T로 변환

std::remove_reference<T>::type // T& T&&에서 &를 제거

std::add_lvalue_ref<T>::type //T를 T&로 변환


이 method들의 구현이 typedef로 구현되어 있기 때문에, ::type이라는 접미사를 붙여야 합니다.


이로 인해서 type이라는 이름을 template에서 사용할때는 유의해야합니다.


C++14에서는 이를 보완한 멋진 template method들이 있습니다.


std::remove_const<T>::type   //C++11

std::remove_const_t<T>      //C++14 에 추가

std::remove_reference<T>::type //C++11

std::remove_reference_t<T>     //C++14에 추가

std::add_lvalue_ref<T>::type //C++11

std::add_lvalue_ref_t<T>     //C++14에 추가


아, 그럼 C++11 사용자는 못쓰는건가????

책에 나온 내용을 보면, C++11 컴파일러를 사용하는 사람도 걱정을 할 필요가 없겠더군요.


지금까지 살펴본 using을 이용하면 아주 쉽게 처리할 수 있습니다.


template <class T>

using remove_const_t = std::remove_const<T>::type;


template <class T>

using remove_reference_t = std::remove_reference<T>::type;


template <class T>

using add_lvalue_ref_t = std::add_lvalue_ref<T>::type;




[참고] effective modern c++ : 항목 9



반응형
반응형


http://blog.naver.com/sandal0394/220495321210


msdn final 링크

https://msdn.microsoft.com/ko-kr/library/jj678985.aspx


msdn sealed 링크


final 키워드는 C++ 11부터 지원되는 키워드로

final 키워드를 가상함수에 사용할 경우, 파생클래스에서 오버라이딩 할 수 없습니다.

final 키워드를 클래스에 사용할 경우, 파생클래스를 정의할 수 없습니다.


sealed 키워드는 C++ 11의 표준 이전에 사용되던 ms에서 비표준 확장입니다.




https://msdn.microsoft.com/ko-kr/library/0w2w91tf.aspx


sealed는 가상 멤버를 재정의할 수 없거나 형식을 기본 형식으로 사용할 수 없음을 나타내는 ref 클래스에 대한 상황에 맞는 키워드입니다.


ISO C++11 표준 언어에는 Visual Studio에서 지원되는 final 키워드가 있습니다.  표준 클래스에서는 final을 사용하고 ref 클래스에서는 sealed를 사용합니다.  

반응형
반응형

[C++11] default 키워드와 delete 키워드


http://jsfumato.tistory.com/10


* default 키워드

C++11에서부터는 default 키워드를 이용하여 명시적으로 default 생성자를 선언할 수 있습니다. 

C++11 이전부터 C++ 클래스는 내부를 작성하지 않아도 

기본적으로 기본생성자, 복사생성자, 대입연산자, 소멸자 네 가지 멤버함수를 생성합니다. 

**C++11부터는 기본 함수에 move 생성자와 move 대입 연산자가 추가되었습니다. 


모두가 알다시피, 위의 기본 함수들 중 생성자와 소멸자에 대해서는 중요한 규칙이 있습니다. 

어떤 종류의 생성자건, 정의된 생성자가 존재한다면, 컴파일러가 기본 생성자를 만들어주지 않는다는 것입니다. 

위와 같은 경우에 default 키워드를 사용하여 명시적으로 생성자를 만들 것을 요구할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
//아래의 클래스는 복사 생성자를 private에 선언했습니다.
//이 경우 다른 기본 클래스는 생성되지 않기에 아래의 main 문은 컴파일 에러를 발생시키게 됩니다.
class MyClass
{
private:
    MyClass(const MyClass&);
};
  
int main()
{
    MyClass nc;
}


위의 경우를 해결하기 위해서는 따로 기본 클래스를 public에 선언 및 정의해주어야 합니다.

이를 더 간단화시키고 명시적으로 보이기 위해서 사용하는 것이 default 키워드입니다.

default 키워드를 사용하면 기본 생성자 또는 복사 생성자의 기본 값을 정의할 필요없이

손쉽게 default 생성자를 만들 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//default 키워드를 사용하여 명시적으로 기본 생성자를 생성했습니다
//물론 이 뿐만 아니라 명시적으로 복사생성자 또는 대입 연산자도 생성할 수 있습니다.
//이 경우 말 그대로 default한 정의를 하기 때문에 깊은 복사는 이루어지지 않습니다.
class MyClass
{
public:
    MyClass() = default;
// 위의 경우 MyClass(){}와 같습니다
private:
    MyClass(const MyClass& class) = default;
// 따로 복사생성자의 내부를 정의할 필요가 없습니다.
};
  
int main()
{
    MyClass nc;
}


* delete 키워드

delete 키워드도 이와 유사한 방법으로 사용할 수 있습니다. 

대표적으로 복사 및 대입이 불가능한 클래스를 만들고 싶을 때를 예로 들 수 있습니다. 


이전까지는 private:에 복사 생성자 및 대입 연산자를 선언하고 정의하지 않는 방식을 이용하여 

외부에서 접근하는 경우 접근 에러, 실수로 public:에 선언한 경우 링크 에러를 유발하는 방식을 사용했다면, 

C++11부터는 delete 키워드를 사용하여 명시적으로 특정 함수에 대한 정의를 금지할 수 있습니다. 


아래의 예시 코드를 살펴봅시다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyClass
{
public:
    MyClass() {}
    ~MyClass() {}
  
// 아래의 생성자 및 연산자가 생성되지 않도록 명시합니다.
// private에 선언하는 방식보다 더 의도가 명확해집니다.
    MyClass(const MyClass& class) = delete;
    MyClass& operator = (const MyClass& class) = delete;
private:
// 아래의 방식이 기존에 사용되던 방식입니다.
// private에 선언하여 외부에서의 접근을 막고
// 정의부를 제외하여 실수로 public에 위치시키는 경우에도 링크 에러를 유발합니다.
// MyClass(const MyClass& class);
// MyClass& operator = (const MyClass& class);
};


delete는 생성자나 연산자 뿐만 아니라 일반 멤버 함수에도 사용할 수 있기에 

이를 응용하여 암시적 형변환이 일어나는 것을 막을 수도 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NoInteger
{
// 아래와 같이 함수를 작성하는 경우 매개변수를 int로 암시적 형변환하여 실행하는 것을 막게됩니다.
// 따라서 foo(13)을 실행하는 경우 에러가 발생합니다.
    void foo(double dparam);
    void foo(int iparam) = delete;
};
  
struct OnlyDouble
{
// 아래의 경우는 double외에는 모든 타입에 대해서 막게됩니다.
    void foo(double d);
    template<typename T> void foo(T) = delete;
};


반응형

+ Recent posts