어떻게 해야 알려주고 싶지 않은 데이터 타입을 숨길 수 있을까요?

C++11로 라이브러리를 만들고 있습니다.
라이브러리는 크게 includesrc 폴더로 이루어져 있습니다.
잘 아시다시피, src 폴더에 있는 여러 cpp 파일을 컴파일하고 나면 링킹 라이브러리 파일이 나오게 되고, 이 라이브러리를 사용하고자 하는 서드파티 프로그램에서 include 폴더를 인클루드하고 해당 라이브러리를 링킹하는 형태로 사용하려고 합니다.

그리고 이 라이브러리는 C 스타일의 옛 라이브러리를 사용합니다. 제가 궁금한 점은, 옛 라이브러리에 있는 여러 자료형과 함수를 제가 만드는 라이브러리에서 쓰게 될 텐데, 이것을 서드파티 프로그램에서 알 필요가 없게 만들고 싶습니다. 왜냐하면 header-only 라이브러리로 만들지 않는 이유와 동일한데, 옛 라이브러리를 각 서드파티 프로그램에서 링킹하고 인클루드하는 것을 막고 싶기 때문입니다.

서론이 길었습니다. 코드를 보여드리겠습니다.

// include/mylibrary/app.h
#include <curses.h> // 이런 헤더 파일과,
class app
{
public:
  app() : win{initscr()} {}
  ~app() noexcept { endwin(); } // 이런 함수와,

private:
  WINDOW* win; // 이런 자료형을 사용하는 것을 숨기고 싶습니다.
};

그래서 Pimpl idiom으로 시도해보았지만, Invalid application of 'sizeof' to an incomplete type '_win_st' 오류로 인해 컴파일을 할 수 없었습니다.

// include/mylibrary/app.h
#include <memory>
class app
{
public:
  app();
  ~app() noexcept {}

private:
  class impl;
  std::unique_ptr<impl> impl_; // 여기서 해당 오류가 발생하는 것 같습니다.
};
// src/app.h
#include <mylibrary/app.h>
#include <curses.h>
class app::impl
{
public:
  impl() : win{initscr()} {}
  ~impl() noexcept { endwin(); }

private:
  WINDOW* win;
};
// src/app.cpp
#include "app.h"

app::app() : impl_{new impl{}}
{}

PImpl idiom으로 해결하는 게 현명한지, 아니면 다른 방법이 있을지 궁금합니다.
많은 조언 부탁드립니다. 읽어주셔서 고맙습니다.

1 Like

Unnamed namespace로 감싸면 안되나요?

으음… app과 app::impl, 그리고 app::app() 부분 전부 namespace { ... } 로 감쌌는데 같은 오류가 뜨네요 ㅜㅜ

엇 API 구현 패턴이네요. impl로 숨기는게 정석 맞습니다.

중요하지 않지만 실제로는 make_unique로 하셨겠죠?

음… 제 눈에 들어오는건 보통 src/app.h를 따로 두지 않고 src/app.cpp 위쪽 부분에 적더라구요.

link 쪽만 문제 없으면 될꺼 같은데… @_@

1 Like

make_unique는 C++14 이상부터 쓸 수 있어서 저렇게 초기화했는데 문제 될 수 있나요?

추가로 제가 확인해보니 라이브러리는 잘 컴파일되는데 test 하는 프로젝트에서 저런 오류가 뜨네요.
필요한 정보를 알려주지 않은 점 죄송합니다.

// test/app.cpp
#include <mylibrary/app.h>

int main()
{
  app myapp{};
}

include/mylibrary/app.h:
Invalid application of 'sizeof' to an incomplete type 'app::impl'

해결했습니다. unique_ptr을 raw pointer나 shared_ptr로 바꾸니 잘 되네요.

unique_ptr을 사용하려면 deleter가 선언 시점에 결정되어야하고, 기본 deleter을 쓸 경우 타입이 불완전해서는 안된다는 것입니다. shared_ptr는 그런 요건이 없으니 불완전해도 되네요.

답변 달아주신 분들 감사합니다!

3 Likes

으음… 이것 때문에 shared_ptr이나 raw pointer로 바꾸신다는건 좀 근본적 해결책이라기보단 workaround 같은데요…

raw pointer는 스마트포인터가 나오게 한 주범이고 shared_ptr은 unique_ptr보다 안전하게 쓰기 훨씬 까다롭죠

Impl 구현체의 메모리에 shared ownership을 주겠다는 것도 이상합니다

다른 대안은 없으려나요…

1 Like

뭐 private으로 숨겨버리니 안에서만 갖고 놀 생각이라면 딱히 상관 없지 않을까요?

// app.hpp
#pragma once

#include <memory>

class App {
 public:
  App();
  ~App();
  int Do();
 private:
  class Impl;
  std::unique_ptr<Impl> impl_;
};

// app.cpp
#pragma GCC visibility push(default)
#include "app.hpp"
#pragma GCC visibility pop

class App::Impl {
 public:
  Impl();
  ~Impl();
  int Do();
};

App::Impl::Impl() {}
App::Impl::~Impl() {}
int App::Impl::Do() { return 72;}

App::App() : impl_(new Impl) {}
App::~App() {}
int App::Do() {
  return impl_->Do();
}
// main.cpp
#include <cstdlib>
#include <iostream>
#include "app.hpp"

int main(int argc, char** argv) {

  App app;

  std::cout << app.Do() << std::endl;

  return EXIT_SUCCESS;
}
# test.sh
g++ -std=c++11 -shared -fPIC -fvisibility=hidden -o app.so app.cpp
g++ -std=c++11 main.cpp -o a.out app.so
LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./a.out

$ ./test.sh 
72

오래전 글타래 입니다만… 생각이 나서… unique도 잘 되네요?

감사합니다. 나중에 시간 날 때 확인해보겠습니다.