2017년 11월 23일 목요일

volatile, std::atomic

volatile
이 메모리에 대한 연산들에는 그 어떤 최적화도 수행하지 말라는 의미의 지시자.
컴파일러 최적화에 의해 volatile 변수를 사용하는 코드가 변경되는 것을 방지.
컴파일러가 해당 변수의 값이 변경될 수 있음을 인지하지 못하고 최적화 처리(예를 들면 변수를 상수로 치환)를 수행하게 되는경우 이를 방지하기 위함


std::atomic 키워드
atomic객체에 대한 연산은 마치 뮤텍스로 보호되는 임계영역안에서 수행되는 것처럼 작동한다.
이런 원자적 연산은 실제 뮤텍스 동작보다 좀 더 효율적인 특별한 기계어 명령들로 구현되는것이 보통.

std::async

Task기반의 쓰레드 처리(내부적으로 쓰레드 풀링 처리를 수행해주고 관리 부담을 덜어준다.)

#include <iostream>
#include <thread>
#include <future>

using namespace std;

bool TaskWork() ///< bool형의 return type
{
    cout << "TaskWork begin, id : " << std::this_thread::get_id() << " \n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    cout << "TaskWork end\n";
   
    return true;
}

int main(int argc, char* argv[])
{
    cout << "Main threadid : " << std::this_thread::get_id() << " \n";
    std::future<bool> future = std::async(std::launch::async, TaskWork);
   
    future.wait();
    cout << "future value : " << future.get() << "\n"; // 위의 "future.wait"이 없으면 future.get()에서 쓰레드가 끝날때까지 블록됨

    return 0;
}


Main threadid : 0x10039d340
TaskWork begin, id : 0x70000eb57000
TaskWork end
future value : 1

std::promise, std::future

promise 객체에 의해 생성된 future객체를 통해 비동기 적으로 획득되는 값이나 예외를 저장할 수 있는 기능을 제공


#include <iostream>
#include <thread>
#include <future>

using namespace std;

void ThreadWork(std::promise<int> promise)
{
    cout << "thread begin\n";
   
    std::this_thread::sleep_for(std::chrono::seconds(1));
    promise.set_value(999); ///< 전달 값 셋팅
   
    cout << "thread end\n";
}


int main(int argc, char* argv[])
{
    // Demonstrate using promise<void> to signal state between threads.
    std::promise<int> promise;
    std::future<int> future = promise.get_future();
    std::thread new_work_thread(ThreadWork, std::move(promise));
   
    cout << "wait future\n";

    future.wait();
    cout << "future value : " << future.get() << "\n"; // 위의 "future.wait"이 없으면 future.get()에서 쓰레드가 끝날때까지 블록됨
   
    cout << "wait thread\n";
    new_work_thread.join();
   
    cout << "end main\n";
    return 0;
}


output
wait future
thread begin
thread end
future value : 999
wait thread
end main


C++ 생성자 및 소멸자 강제 호출(malloc + free)


class A
  {
  public:
      A() {
          cout << "constructure\n";
      }

      A(int i) {
          cout << "constructure. i : " << i << "\n";
      }

      ~A() {
          cout << "destructor\n";
      }

      virtual void foo ()
      {
          cout << "foo\n";
      }
  };

A* p = static_cast<A*>(malloc(sizeof(A)));  ///< 생성자를 호출하지 않고 객체 생성
new(p) A(11);                               ///< 생성자 호출(placement new), 이부분 빼면 크래쉬(가상함수 테이블이 초기화가 안되었기 때문에)
p->foo();

p->~A();                                    ///< 소멸자 호출
free(p);                                    ///< 객체 제거

output
constructure. i : 11
foo
destructor

*숨겨진 생성자의 기능
  -부모 클래스가 있다면 부모 클래스의 생성자 호출
  -객체인 멤버변수들이 있다면 이들의 생성자 호출
  -가상함수가 있을 경우 가상함수 테이블 초기화

#trivial(생성자를 호출할 필요가 없는)한 객체인가를 판단하는 함수 : is_trivially_default_constructible

#include <type_traits>
if(false == std::is_trivially_default_constructible<B>::value)
        new(p) B(11);                               //< 생성자 호출(placement new)
 



constexpr를 사용한 성능 최적화

constexpr 키워드는 C++11에서 도입되고 C++14에서 향상됨.
const와 마찬가지로, 코드에서 값을 수정하려고 하면 컴파일러 오류가 발생하도록 변수에 적용할 수 있음.

const와 달리, constexpr은 함수 및 클래스 생성자에도 적용할 수 있음!..

constexpr 함수를 사용하는 경우 컴파일 타임에 계산가능한 경우는 반환값에 대한 상수값을 미리 생성하고, 그렇치 않은 경우는 일반함수처럼 런타임에 값을 생성함. (2가지 유형 모두 사용될 수 있음)
(디버깅 브레이크 포인터로 함수내 코드가 브레이크가 잡히는지로 확인 가능)

런타임 대신 컴파일 타임에 값을 계산할 수 있는 경우 프로그램이 **더 빨리 실행되고 더 적은 메모리**를 사용하는 데 도움이 된다.

constexpr float exp(float x, int n) 

    return n == 0 ? 1 : 
        n % 2 == 0 ? exp(x * x, n / 2) : 
        exp(x * x, (n - 1) / 2) * x; 
}; 

중괄호를 사용한 복수 데이터 초기치값 전달은 std::initializer_list를 사용하여 구현할 수 있음.(C++11)

template<typename T>
void f(std::initializer_list<T> initList)
{
    std::cout << "constructed with a " << initList.size() << "-element list\n";

    for(auto i = l.begin(); i != l.end(); i++)
    {
        std::cout << "element " << *i << "\n";
    }
}

std::initializer_list<int> f2()
{
    return { 1, 2, 3 };
}


f({10, 20, 30});
f(f2());


output
constructed with a 3-element list
element 10
element 20
element 30
constructed with a 3-element list
element 1
element 2
element 3

템플릿 함수에서의 Universal reference 사용

template <typename T>
void t_function(T& x) {}


위와 같이 템플릿을 만든다면 Rvalue 값을 사용할 때 이 템플릿을 사용하지 못하게 됨.
int val = 4;
t_function(val); // 가능
t_function(4); // 불가능, 적용 대상이 되는 템플릿 함수가 없음.


상수값 대응을 위애 아래처럼 2가지로 나눈다면...
template <typename T>
void t_function(T &x) {}

template <typename T>
void t_function(T x) {}

t_function(x); // 에러, 위 2가지 템플릿 모두 해당되어 ambiguous 오류
t_function(4); // 가능


위 모두 만족시킬수 있는 방법이 Universal References

template <typename T>
void t_function(T&& x) {}

배열 개수를 참조하는 템플릿 함수

template<typename T, std::size_t N>
void f(T (&param)[N])
{
    std::cout << "param address : " << param << ", size : " << N << "\n";
}

int x[] = { 1, 2, 3, 4 };
f(x);

output
param address 0x7ffeefbff5b0, size : 4
Program ended with exit code: 0