살군의 보조기억 장치

Another memory device…

Archive for February 2014

함수 시그너처에서 변수명이 생략된 경우

with one comment

여기에서 한 번 언급했던 내용인 함수 시그너처signature 가운데 매개변수parameter가 생략 혹은 주석처리된 경우를 본 적이 있는가? 물론 컴파일이 된다. 되니까 이야기를 시작한 것이지. 단, c++에서만. c에서는 컴파일이 안된다.

여기에 대해 궁금하게 된 이유는 이제 opencv test code 를 만들어 볼려고 이미 잘 작성된 기존 테스트코드를 살펴보다가 발견했다. 아래 코드를 보면,

class Core_ArrayOpTest : public cvtest::BaseTest
{
public:
    Core_ArrayOpTest();
    ~Core_ArrayOpTest();
protected:
    void run(int);
};

//...

void Core_ArrayOpTest::run( int /* start_from */)
{
    //...
}

line:7에서는 선언부이므로 생략하는 것이 문제가 없지만, 일반적으로 line:12의 정의부에서는 매개변수 이름을 생략하는 것이 일반적이지는 않다. stackoverflow를 찾아보니 당연히 누군가가 질문을 했었고 몇가지 쓸데없는 언쟁(?)도 있고 답도 있다.

정리하면, 매개변수 이름이 생략되는 것은 c++ 표준에서 허용하고 있으니까 이건 괜찮지만, c의 경우에는 c99 표준에서 명시적으로 허용하지 않기 때문에 매개변수 이름을 생략하지 못한다. 흠… 표준에서 정한 것이니 할말은 없다. 그치만 왜 이렇게 허용을 했을까?

일반적인 사용의 예는 위의 코드에서처럼 인터페이스에서 어떤 목적으로 함수에 매개변수가 필요하다고 선언을 했지만, 실제 구현시에 매개변수명을 주석처리해 놓고 있다. 이름을 주석처리했으니 구현한 함수 내에서는 매개변수를 사용할 수가 없다. 즉, 이 인터페이스를 구현한 다른 함수에서는 필요하지만 이 함수에서는 사용하지 않을 경우 이렇게 주석으로 처리한다. 이렇게 하면 컴파일러에서 unused warning 도 발생하지 않는다. 아마도 이런 이유 때문에 c++에서는 이 방식을 허용한 것이 아닌가 추측된다.

참고

Written by gomiski

2014/02/26 at 7:42 am

typedef 으로 함수포인터 정의하기

leave a comment »

opencv 코드를 보면서 평소에 잘 사용하지 않는 다양한 문법들을 보게된다. 지금 소개하게될 내용도 그런것 가운데 하나다. typedef 를 사용해서 함수포인터function pointer를 정의하는 것이다. 첨 보면 잘 이해가 되지 않는 부분이다. 우선 이 문제를 보게된 것은 아래 코드를 살펴보면서이다.

void TS::init( const string& modulename )
{
    char* datapath_dir = getenv("OPENCV_TEST_DATA_PATH");

    if( datapath_dir )
    {
        char buf[1024];
        size_t l = strlen(datapath_dir);
        bool haveSlash = l > 0 && (datapath_dir[l-1] == '/' || datapath_dir[l-1] == '\\');
        sprintf( buf, "%s%s%s/", datapath_dir, haveSlash ? "" : "/", modulename.c_str() );
        data_path = string(buf);
    }

    cv::redirectError((cv::ErrorCallback)tsErrorCallback, this);

    if( ::testing::GTEST_FLAG(catch_exceptions) )
    {
        // ...

뭔가 오묘한 느낌이 드는 코드이다. callback 함수를 cv::redirectError() 에 넘기는데 cv::ErrorCallback 타입으로 변환해서 넘긴다. 그럼 cv::ErrorCallback 타입이 뭘까를 봐야된다. 당연히 찾아봤다. core.hpp 에 정의가 되어있다. 코드를 살펴보면,

typedef int (CV_CDECL *ErrorCallback)( int status, const char* func_name,
                                       const char* err_msg, const char* file_name,
                                       int line, void* userdata );

위와 같이 정의되어 있다. 자 이제부터 이번 글의 내용인 typedef 에 대한 문제가 나온다. typedef 는 뭔지 다 알테고 int 를 뭔가로 대체하는데 이 대체하기로 한 놈이 무지막지하게 이상한 형태로 보인다. 뒷쪽은 분명 함수의 형태인데 앞에는 typedef 이다. 결론부터 이야기하면 이런 형태가 typedef 로 만든 함수포인터이다. int 값을 리턴하는 ErrorCallback 함수포인터를 정의한 것이다.

우선 함수포인터가 무엇인지를 살펴보자. 함수포인터는 말 그대로 함수에 대한 포인터이다. 사용법은,

void (*func_pointer)(int, char);        // 선언

                         // foo 라는 함수가 있을 때,
func_pointer = foo;      // 초기화
func_pointer = &foo;     // 초기화 (둘다 가능하다)

func_pointer(arg1, arg2);               // 함수포인터 호출
(*func_pointer)(arg1, arg2);            // 함수포인터 호출 (둘다 가능)

위의 코드와 같다. 함수포인터라는 놈이 함수를 지정하는 포인터이고 이를 선언, 초기화, 호출하는 방법은 위와 같이 한다. 초기화는 함수이름을 그대로 사용하거나 혹은 함수이름 앞에 & 를 넣어서 할당할 수도 있다. 호출의 경우 일반 함수처럼 이름다음에 바로 인자를 넘길 수도 있고 포인터처럼 사용할 수도 있다. 둘다 정상적인 사용법이다.

이제 typedef 에서 함수포인터를 어떻게 사용하는지 살펴볼 차례다. stackoverflow 에서 잘 설명해주신 분이 있어서 그대로 발췌해왔다.

Is a function pointer created to store the memory address of a function? Yes, a function pointer stores the address of a function. This has nothing to do with the typedef construct which only ease the writing/reading of a program ; the compiler just expands the typedef definition before compiling the actual code.

typedef int (*t_somefunc)(int,int);
int square(int u, int v) { return u*v; }

t_somefunc afunc = □
// ...
int x2 = (*afunc)(123, 456); // call square() to calculate 123*456

위의 line:1 에서 의미하는 것은 두가지이다. 하나는 typedef 로 함수포인터를 선언하는 것이고, 다른 하나는 함수포인터가 typedef 에 의해 새로운 type 형이된다는 것이다. 즉, int 리턴값을 가지는 함수포인터 자체를 변수의 한 타입으로 만들어버린다. 자! 그럼 이제 다시 처음에 봤던 코드로 돌아가보자. 여기서 필요한 부분을 줄여서 쓰면,

typedef int (*ErrorCallback)(types args);

// ...

CV_EXPORTS ErrorCallback redirectError( ErrorCallback errCallback,
                                        void* userdata=0, void** prevUserdata=0);

// ts.cpp 에 정의된 tsErrorCallback() 함수
static int tsErrorCallback( int status, const char* func_name, const char* err_msg, const char* file_name, int line, TS* ts )
{
    ts->printf(TS::LOG, "OpenCV Error: %s (%s) in %s, file %s, line %d\n", cvErrorStr(status), err_msg, func_name[0] != 0 ? func_name : "unknown function", file_name, line);
    return 0;
}

가 된다. CV_CDECL 은 windows 에서 사용하는 prefix 로 관련 내용은 구글링을 하면 쉽게 무슨내용인지 알 수 있을 것이다. 코드에서 수행되는 내역과 상관없이 컴파일러단에서 뭔가를 처리해달라는 것으로 지금은 무시해도 된다. 자… 위에서 요약한 내요을 보니 typedef 로 선언한 함수포인터라는 것이 딱 드러났다. line:198 은 cv::ErrorCallback을 사용하는 모습을 보여준다. 그리고 실제 opencv 소스상에는 cv::ErrorCallback 에 대한 정의가 없다. 대신 위의 코드에서 보듯이 필요로하는 callback 함수를 만들어서 cv::ErrorCallback 타입으로 형변환 한 다음 사용하는 것을 볼 수 있다.

이제 다시한번 제일 위에 코드를 보면 쉽게 읽을 수 있을 것이다.

참고

Written by gomiski

2014/02/24 at 2:17 am

class 초기화, member 초기화

leave a comment »

c++98 이후에 정의된 class 초기화와 member 초기화에 대해 정리를 해야겠다는 생각이 들었다. 평소에 자주 보던 문법이 아니라 자꾸 햇갈려서. 특히 base class 생성자의 초기화 부분은 매번 햇갈리다보니 정리를 해 놓아야지 안그럼 또 검색 -> 이해 -> (시간이 지나면) -> 검색 -> 이해… 단계를 계속 할 것 같았다. 말로 설명하는 것 보단, 표준이니까 그냥 코드를 보고 쓰는 방법을 배우면 된다. 자 일단 struct 내용을 살펴보자.

struct Class : public Base
{
    int x;
    int y;

    Class ( int x )
      : Base ( 123 ), // initialize base class
        x ( x ),      // x (member) is initialized with x (parameter)
        y ( 0 )       // y initialized to 0
    {}                // empty constructor body

    Class ( double a )
      : y ( a+1 ),
        x ( y ) // x will be initialized before y, this means that its value here is undefined
    {}          // No base class constructor in list, this is the same as calling Base()

    Class()
    try
      : Base ( 789 ),
        x ( 0 ),
        y ( 0 )
    {
        // no exception
    }
    catch (...)
    {
        // exception occurred on initialization
    }
};

위에 하이라이트한 line:6~10 을 보면 base struct 생성자를 어떻게 정의하는지를 볼 수 있다. class 도 마찬가지로 사용할 수 있다. 여기서 하나 짚고 넘어갈 부분은 line:17 부터 있는 생성자의 try {} catch {} 문이다. 이렇게도 사용할 수가 있는 것이다. 흠흠… 이렇게 사용하는건 처음봤다.

class 의 경우도 사용방법은 동일하다. 예제코드를 보면,

// Declare class MyClass.
class MyClass
{
public:
   MyClass( int rSize ) {}
};

//  Declare class DialogBox, derived from class MyClass
class DialogBox : public MyClass
{
public:
   DialogBox( int rSize );
};

//  Define the constructor for DialogBox. This constructor
//   explicitly initializes the MyClass subobject.
DialogBox::DialogBox( int rSize ) : MyClass( rSize )
{
}

위와 같이 사용이 가능하다. 위의 예는 선언 declaration과 정의definition을 분리한 경우이고 아래 예는 선언과 동시에 정의한 것이다. 사용하는데 햇갈리지만 않으면 어느쪽이든 상관이 없다.

   // ...
   DialogBox( int rSize ) : MyClass( rSize ) {}
   // ...

자바의 super() 와 비슷한 기능을 한다고 볼 수 있다. 혹시 다중상속은 어떻게 쓰는지 궁금하다면 Initializing base classes and members (C++ only)을 보면 된다.

참고

Written by gomiski

2014/02/21 at 1:44 am

TEST() {…} 매크로

leave a comment »

Google test 에서 실제 테스트를 사용하는 것을 살펴보자. opencv_test_core 의 test_eigen.cpp 을 기준으로 찾아보겠다.

TEST(Core_Eigen, scalar_32) {Core_EigenTest_Scalar_32 test; test.safe_run(); }

와 같이 사용한다. 이 TEST() 매크로는 뭘 하는 것일까? 역시 하나씩 파고 들어가보자.

// Define this macro to 1 to omit the definition of TEST(), which
// is a generic name and clashes with some other libraries.
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif

라고 되어있다. 위 코드에 있는 GTEST_TEST 매크로는 뭐냐하면,

#define GTEST_TEST(test_case_name, test_name)\
  GTEST_TEST_(test_case_name, test_name, \
              ::testing::Test, ::testing::internal::GetTestTypeId())

이다. 또 매크로다.

// Helper macro for defining tests.
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
 public:\
  GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
 private:\
  virtual void TestBody();\
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
  GTEST_DISALLOW_COPY_AND_ASSIGN_(\
      GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ =\
    ::testing::internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, NULL, NULL, \
        (parent_id), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        new ::testing::internal::TestFactoryImpl<\             
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()

이건 어마어마하다. 도데체 어떻게 이런 생각을 했는지 모르겠다. line:16 에 있는 # 은 그대로 스트링으로 만들어주는 것이다. 사용 예는 여기에서 볼 수 있다. 위의 코드에 있는 또 다른 매크로들을 살펴보자.

// Expands to the name of the class that implements the given test.
#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
  test_case_name##_##test_name##_Test

// GTEST_ATTRIBUTE_UNUSED_
# define GTEST_ATTRIBUTE_UNUSED_

// GTEST_DISALLOW_COPY_AND_ASSIGN_
// A macro to disallow copy constructor and operator=
// This should be used in the private: declarations for a class.
#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type)\
  type(type const &);\
  GTEST_DISALLOW_ASSIGN_(type)

위에서 본 ## 은 매크로에서 사용하는 것으로 지난번 포스팅에서도 한번 언급을 했는데, 빈 칸을 붙여주는 것이다.
자… 이제 대충 다 찾은거 같다. 이제 맨 처음으로 가서 TEST() 매크로를 하나씩 풀어보자. 이건 정말로 대단히 대단하고 굉장히 굉장한 매크로다. 이런 복잡한 매크로는 본적이 없다. 클래스 선언을 통째로 매크로로 만들어버리는 어마어마한 놈들이다. 거기에 클래스 생성은 팩토리 패턴으로 생성해버린다. 입이 딱 벌어지는 구현이다. 아래 코드가 모든 매크로를 다 풀어쓴 것이다. 제일 위에 있는 한줄의 코드를 풀어쓰면 아래와 같이 된다.

class Core_Eigen_scalar_32_Test : public ::testing::Test {
 public:
  Core_Eigen_scalar_32_Test() {}
 private:
  virtual void TestBody();
  static ::testing::TestInfo* const test_info_ ;
  Core_Eigen_scalar_32_Test(Core_Eigen_scalar_32_Test const &);
  void operator=(Core_Eigen_scalar_32_Test const &);
};

::testing::TestInfo* const Core_Eigen_scalar_32_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        "Core_Eigen", "scalar_32", NULL, NULL,
        (::testing::internal::GetTestTypeId()),
        ::testing::Test::SetUpTestCase,
        ::testing::Test::TearDownTestCase,
        new ::testing::internal::TestFactoryImpl<Core_Eigen_scalar_32_Test>);
void Core_Eigen_scalar_32_Test::TestBody() { Core_EigenTest_Scalar_32 test; test.safe_run(); }
참고

Written by gomiski

2014/02/17 at 12:08 pm

BaseTest – ts.hpp

with 2 comments

뭐가 좀 되나 싶으니 또 다른게 나오는구나. 아니 사실 무시하고 넘어갈 수 있을꺼라 생각했는데 결국 다 이유가 있어서 사용하는 것으로 보이는 놈을 분석하기 위해 다시 ts.hpp 파일로 돌아왔다. 실제 test 를 구현한 코드를 살펴보면 BaseTest 를 상속받아서 정의한 클래스들이 있다. 그러니 이걸 살펴볼 수 밖에… 언제 테스트코드를 작성하나.

class CV_EXPORTS BaseTest
{
public:
    // ...
    virtual void run( int start_from );            // the main procedure of the test
    virtual void safe_run( int start_from=0 );     // the wrapper for run that cares of exceptions
    virtual bool can_do_fast_forward();            // returns true if and only if the different test cases do not depend on each other
                                                   // (so that test system could get right to a problematic test case)
    virtual void clear();                  // deallocates all the memory.
                                           // called by init() (before initialization) and by the destructor
protected:
    // ...
    virtual int read_params( CvFileStorage* fs );              // read test params
    virtual int get_test_case_count();                         // returns the number of tests or -1 if it is unknown a-priori
    virtual int prepare_test_case( int test_case_idx );        // prepares data for the next test case. rng seed is updated by the function
    virtual int validate_test_results( int test_case_idx );    // checks if the test output is valid and accurate
    virtual void run_func();                                   // calls the tested function. the method is called from run_test_case() runs tested func(s)
    virtual int update_progress( int progress, int test_case_idx, int count, double dt );      // updates progress bar
    const CvFileNode* find_param( CvFileStorage* fs, const char* param_name );                 // finds test parameter

    string name;       // name of the test (it is possible to locate a test by its name)
    TS* ts;            // pointer to the system that includes the test
};

막상 적어놓고 보니, 이게 뭘 하는지는 전혀 감이오지 않는다. 설정하는 부분만 잔뜩 있고 실제 뭔가 어떤 일을 하는 부분은 대부분 virtual 키워드로 되어 있으므로, 실제 코드를 사용하는 테스트케이스를 살펴봐야 자세한 내용을 알 수 있겠다. 그전에 BaseTest가 정의된 ts.cpp 의 내용을 먼저 살펴보자.

const CvFileNode* BaseTest::find_param( CvFileStorage* fs, const char* param_name )
{
    CvFileNode* node = cvGetFileNodeByName(fs, 0, get_name().c_str());
    return node ? cvGetFileNodeByName( fs, node, param_name ) : 0;
}

// ...

void BaseTest::safe_run( int start_from )
{
    read_params( ts->get_file_storage() );        // 실제로는 아무 일도 하지 않는다.
    ts->update_context( 0, -1, true );            // 중복사용? 왜 이렇게 쓴는거지?
    ts->update_context( this, -1, true );         // this 를 test case 로 사용

    if( !::testing::GTEST_FLAG(catch_exceptions) )    // test 시 옵션을 주는 것으로, 사용되지 않는다.
        run( start_from );                            // 개념상으로 보면 gtest 에서 catch_exceptions 를
    else                                              // 정의했다면 바로 run() 을 수행하고,
    {                                                 // 아닐 경우 try {} catch {} 로 exception 핸들링
        try
        {
        #if !defined WIN32 && !defined _WIN32
        int _code = setjmp( tsJmpMark );
        if( !_code )
            run( start_from );
        else
            throw _code;
        #else
            run( start_from );                // 실재 windows7 기반으로 수행될 경우, 실행되는 부분
        #endif
        }
        catch (const cv::Exception& exc)
        {
            const char* errorStr = cvErrorStr(exc.code);
            char buf[1 << 16];             
            sprintf( buf, "OpenCV Error: %s (%s) in %s, file %s, line %d", 
                    errorStr, exc.err.c_str(), exc.func.size() > 0 ?
                    exc.func.c_str() : "unknown function", exc.file.c_str(), exc.line );
            ts->printf(TS::LOG, "%s\n", buf);
            ts->set_failed_test_info( TS::FAIL_ERROR_IN_CALLED_FUNC );
        }
        catch (...)
        {
            ts->set_failed_test_info( TS::FAIL_EXCEPTION );
        }
    }

    ts->set_gtest_status();
}

void BaseTest::run( int start_from )                     // 내가 타겟으로 잡은 test_eigen.cpp 에서는
{                                                        // 클래스에서 새로 run() 을 선언했으므로, 
    int test_case_idx, count = get_test_case_count();    // 실제 사용되지는 않는다.
    int64 t_start = cvGetTickCount();                    // 다른 테스트케이스의 경우 사용될 수도 
    double freq = cv::getTickFrequency();                // 있을 것이다.
    bool ff = can_do_fast_forward();
    int progress = 0, code;
    int64 t1 = t_start;

    for( test_case_idx = ff && start_from >= 0 ? start_from : 0;
         count < 0 || test_case_idx < count; test_case_idx++ )
    {
        ts->update_context( this, test_case_idx, ff );
        progress = update_progress( progress, test_case_idx, count, (double)(t1 - t_start)/(freq*1000) );

        code = prepare_test_case( test_case_idx );
        if( code < 0 || ts->get_err_code() < 0 )
            return;

        if( code == 0 )
            continue;

        run_func();

        if( ts->get_err_code() < 0 )
            return;

        if( validate_test_results( test_case_idx ) < 0 || ts->get_err_code() < 0 )
            return;
    }
}

// ...

코드를 분석하고 보니, 여기서 많은 일들이 처리된다. line:195 의 GTEST_FLAG() 내용을 살펴보면,

// Macro for referencing flags.
#define GTEST_FLAG(name) FLAGS_gtest_##name

라고 정의되어 있다. 여기서 ## 은 두가지 내용을 공백없이 이어붙이는 역할을 한다. 여기서 사용예를 살펴볼 수 있다. 코드에서 처럼 GTEST_FLAG(catch_exceptions) 로 호출할 경우, 결과값은 FLAGS_gtest_catch_exceptions 이된다.

나머지 분석은 다음 포스트에서…
line:221 의 catch(…) 구문도 평소에 보기 힘든 것이다. c++의 exception 부분을 살펴보면,

If an ellipsis (…) is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown. This can be used as a default handler that catches all exceptions not caught by other handlers:

try {
  // code here
}
catch (int param) { cout << "int exception"; }
catch (char param) { cout << "char exception"; }
catch (...) { cout << "default exception"; }

In this case, the last handler would catch any exception thrown of a type that is neither int nor char.

After an exception has been handled the program, execution resumes after the try-catch block, not after the throw statement!.

즉, 미리 정의하지 않은 exception 이 발생할 경우 default exception handler 로 동작하는 것이다. 아직 정확하게 어떻게 동작하는지는 잘 모르겠지만 대충 어떻게 되는 건지는 감이 온다.

참고

Written by gomiski

2014/02/17 at 6:34 am

Google Test 사용법

leave a comment »

opencv 가 google test 를 사용해서 unit test 를 하니까 google test 에 대해서도 한번 살펴봐야겠다. 물론 내가 첨부터 하진 않을꺼고 훌륭하신 분이 이미 앞서 잘 정리를 해놓은거를 참고하겠다. 여기서 살펴볼 것은 google test 를 시작하기 위해 필요한 코드이다. 예제코드를 보면 쉽게 지금 opencv 에서 어떻게 사용하는지를 알 수 있을 것이다. 기본적인 설정은 opencv 빌드환경을 만들기 위해 cmake 할 때 자동으로 다 설정이 되어 있으니 지금 내게 필요한 것은 어떻게 사용하는가 하는 것이다.

int _tmain(int argc, _TCHAR* argv[])
{
   /*The method is initializes the Google framework and must be called before RUN_ALL_TESTS */
   ::testing::InitGoogleTest(&argc, argv);

   /*RUN_ALL_TESTS automatically detects and runs all the tests defined using the TEST macro.
   It's must be called only once in the code because multiple calls lead to conflicts and,
   therefore, are not supported.
   */
   return RUN_ALL_TESTS();
}

위에 하이라이트한 line:4 와 line:10 을 보면 어디서 많이 본 것을 알수 있다. 그렇다. CV_TEST_MAIN 매크로 안에 있는 몇줄 안되는 코드 가운데 두 줄이다. 주석을 보면, line:4 는 구글테스트 프레임웍을 사용하기전에 반드시 선언해서 초기화 해줘야 되는 것이고, line:10 은 구글테스트가 실제 테스트를 수행하라고 하는 명령이다. 나는 구글테스트를 분석하는 것이 아니므로 이놈들이 뭘 의미하는지만 확인하면 된다.

이제 덤으로, 구글테스트의 몇가지 구문을 보자. 이것도 물론 훌륭하신 분이 미리 잘 정리해 둔 것이 있어 그대로 인용하겠다.

구글테스트가 지원하는 어설션은 2가지 종류로 나뉘는데 하나는 ASSERT_* 버전이고, 하나는 EXPECT_* 버전이에요. 둘의 차이는 뭘까요? 어설트* 버전은 실패 시 해당 테스트를 바로 종료해버리고 익스펙트* 버전은 실패 하여도 테스트를 계속 진행합니다.

True / False

치명적인 어설션

어설션

검증하는 것

ASSERT_TRUE(상태)

EXPECT_TRUE(상태)

상태가 참인지

ASSERT_FALSE(상태)

EXPECT_FALSE(상태)

상태가 거짓인지

 
Comparison

치명적인 어설션

어설션

검증하는 것

ASSERT_EQ(기대, 실제)

EXPECT_EQ(기대, 실제)

기대 == 실제

ASSERT_NE(값1, 값2)

ASSERT_NE(값1, 값2)

값1 != 값2

ASSERT_LT(값1, 값2)

ASSERT_LT(값1, 값2)

값1 < 값2

ASSERT_LE(값1, 값2)

ASSERT_LE(값1, 값2)

값1 <= 값2

ASSERT_GT(값1, 값2)

ASSERT_GT(값1, 값2)

값1 > 값2

ASSERT_GE(값1, 값2)

ASSERT_GE(값1, 값2)

값1 >= 값2

 
String comparison

치명적인 어설션

어설션

검증하는 것

ASSERT_STREQ(기대, 실제)

EXPECT_STREQ(기대, 실제)

두 문자열이 같은지 판단

ASSERT_STRNE(값1, 값2)

EXPECT_STRNE(값1, 값2)

두 문자열이 다른지 판단

ASSERT_STRCASEEQ(v1, v2)

EXPECT_STRCASEEQ(v1, v2)

두 문자열이 같은지

(대소문자무시)

ASSERT_STRCASENE(v1, v2)

EXPECT_STRCASENQ(v1, v2)

두 문자열이 다른지

(대소문자 무시)

돌고 돌아왔는데, 다시 코드를 살펴보면 별 다른게 없다는걸 확인할 수 있다. 그렇다. 나는 아직도 이렇게 삽질을 하고 있다. ㅠㅠ 슬슬 뭔가 할 수 있는 단계에 들어서고 있다!

참고

Written by gomiski

2014/02/17 at 2:27 am

<< 연산자 오버로딩 – ts_func.cpp

leave a comment »

이전 포스트에서 ts.hpp 를 봤었는데, 여기에서 << 연산자 오버로딩의 내용을 살펴보자. 이를 살펴보려면 우선 MatInfo가 무엇인지를 봐야된다.

struct CV_EXPORTS MatInfo
{
    MatInfo(const Mat& _m) : m(&_m) {}
    const Mat* m;
};

이런… 시작하자마자 암호같은 코드가 나왔다. line:160 은 도데체 무슨말일까? struct 타입인데 생성자 처럼 생긴 뭔가가 있고 거기에 콜론과 중괄호 까지 같이 쓰고 있다. 역시나 stackoverflow 에 질문한 사람도 있고 친절하게 답한 사람도 있다.

In C++ the only difference between a class and a struct is that members and base classes are by default private in classes, while in structs they default to public. So structures can have constructors, and the syntax is the same as for classes.

struct TestStruct { 
        int id;
        TestStruct() : id(42) { }
};

아! 이런 이건 예전에 봤던 거였네. 좀처럼 볼 일이 없는 형식이라 까먹고 있었다. 다시보면, MatInfo 는 matrix 포인터를 가지는 구조체이다. 그 이상의 기능도 없다. 그럼 왜 이렇게 할까? 이유를 잘 모르겠다. matrix 변경을 막으려고 한다면 포인터 변경을 막는 const 도 추가해야 되기 때문이다. 지금의 상태는 값 변환은 못하도록 막지만, 포인터 자체가 변할 경우에는 그대로 변경이 되기 때문이다. 이건 실제 사용코드를 봐야되니까 다음 기회에 찾아봐야겠다.

std::ostream& operator << (std::ostream& out, const MatInfo& m) 
{     
    if( !m.m || m.m->empty() )
        out << "";
    else
    {
        static const char* depthstr[] = {"8u", "8s", "16u", "16s", "32s", "32f", "64f", "?"};
        out << depthstr[m.m->depth()] << "C" << m.m->channels() << " " << m.m->dims << "-dim (";
        for( int i = 0; i < m.m->dims; i++ )
            out << m.m->size[i] << (i < m.m->dims-1 ? " x " : ")");
    }
    return out;
}

위는 ts_func.cpp 에서 연산자 오버라이딩을 실제 구현한 코드이다. 대충 살펴보면 matrix 정보에서 depth, channel, dim 등을 표시하는 거다.

<< 연산자는 이전글에서 설명한 것 같이 bitwise 연산을 위한 연산자 오버로딩도 있지만, 여기서 사용한 방식은 출력 연산자 오버로딩이다. 즉, ostream 의 출력방식을 전역으로 처리한 것이다. 마침 내가 햇갈리던 부분을 잘 설명한 글이 있어 그대로 인용한다.

메인함수를 보자. 어떤 Point 클래스가 있고, 이 Point 클래스를 p라는 객체를 통해 생성하고 있고, 이 객체를 cout을 이용해 출력하려는 문장이다. 하지만 기본적으로 cout은 기본자료형만 출력을 하지 객체인 사용자 타입의 자료형은 출력을 할 수가 없다. 4번째 줄을 멤버 함수로의 오버로딩 해석에 의해 풀어 써 보자.
 cout.operator<< (p) 즉, p객체를 인자값을 받을 수 있도록 cout 객체에는 operator<< 연산자가 오버로딩 되어 있어야 하는데, 표준 std의 ostream 클래스에서도 쉬프트 연산에 대한 오버로딩이 정의 되어 있지 않다. 그래서 우리가 표준 라이브러리를 건드리면서까지 객체를 출력하는 일은 불가능 할 것이다.

이로서 멤버 함수로의 오버로딩으로는 객체를 출력할 수 없다는 것을 알 수 있었다. 그럼 전역 함수의 오버로딩을 보자.전역함수로의 오버로딩에서는 cout << p 문장은 operator (cout, p); 로 풀어 써지고, 이것은 논리적으로 문제가 없다. 그럼 전역함수로 오버로딩으로 다음과 같이 함수를 정의 할 수 있겠다.

기존의 오버로딩은 functionName.operator() 형식이였는데, 출력연산자의 경우에는 위 코드의 line:2790 처럼 항상 std::ostream& operator << (std::ostream& out, const paramType& param) 이 되는 것이다. 실제 cout 을 호출할 때를 생각하면 전달 parameter 가 하나밖에 없게 되는 부분이 좀 이해가 안됐는데, 위의 글에서 명쾌하게 해결이 되었다.

참고

Written by gomiski

2014/02/13 at 6:40 am

Posted in C++, Lecture, opencv

Tagged with , , , ,