Static Library i dependent (internal) headers

0

Pierwszy raz w życiu piszę własnego static lib'a i nie mam pojęcia coś takiego zmontować do kupy. Internet jest pełen przykładów, ale wszędzie robią to w najprostszej formie, czyli deklaracja funkcji w .h, definicja w .cpp i tyle. Mnie natomiast zastanawia jak w bibliotece umieścić klasę, która będzie miała dostęp do innych klas (każda w osobnym .h i .cpp), ale bez konieczności dystrybuowania ich nagłówków razem z biblioteką. Coś takiego:

//Core.h

#include "Point.h"
#include "Triangle.h"

class Core
{
  Point P;
  Triangle T;
}

Chciałbym to zorganizować tak, żeby dołączać sam .lib i Core.h ale już nie dziesiątki innych header'ów jak Point.h, Triangle.h itd. One i tak nie są przeznaczone dla końcowego użytkownika, tylko na wewnętrzne potrzeby klasy Core.

Jak coś takiego się robi?

2

Nie możesz wmontować *.h do *.lib. Do biblioteki pakujesz kod, czyli funkcje.

//AllMy.h
#include "Point.h"
#include "Triangle.h"
#include "Core.h"

Wszędzie dołączasz tylko #include "AllMy.h"

0
_13th_Dragon napisał(a):

Nie możesz wmontować *.h do *.lib. Do biblioteki pakujesz kod, czyli funkcje.

Wiem, ale mnie właśnie zastanawia czy da się to jakoś obejść. Biblioteki często są dystrybuowane tylko z plikiem .lib i pojedynczym nagłówkiem, choć wewnętrznie korzystają z wielu klas.

1

Jeżeli dana klasa nie wychodzi poza wewnętrzne bebechy biblioteki to nie ma żadnego problemu, ale jak widzę ty chcesz mieć te *.h na zewnątrz biblioteki.

2

Wygląda na to, że potrzebujesz interfejsów i implementacji interfejsów w bibliotece.

Nie ma jednego konkretnego sposobu na zrobienie tego. Zwłaszcza kiedy mowa o kompatybilności wstecznej.

Często implementuje się to używając metod wirtualnych.

Drzewo plików:

Public/Core.h
Private/CoreImpl.h
Private/Point.h
Private/Triangle.h
Private/CoreImpl.cpp

To co jest w Public/ jest dla użytkownika końcowego biblioteki. To co jest w Private/ jest implementacją biblioteki.
Często stosowaną konwencją są określenia include (odpowiednik Public), i src (odpowiednik Private).

// Public/Core.h
#pragma once

class Core
{
public:
  virtual void SomeMethod() = 0;
};

Core *CreateCore();
void DestroyCore( Core *core );

// Private/Point.h
#pragma once

class Point
{
};
// Private/Triangle.h
#pragma once

class Triangle
{
};
// Private/CoreImpl.h

#include "Point.h"
#include "Triangle.h"

class CoreImpl : public Core
{
public:
  virtual void SomeMethod() override;

private:
  Point m_point;
  Triangle m_triangle;
};
// Private/CoreImpl.cpp

#include "CoreImpl.h"

void CoreImpl::SomeMethod()
{
 // Implementacja funkcji Core::SomeMethod()
}

Core *CreateCore()
{
  return new CoreImpl;
}

void DestroyCore( Core *core )
{
  delete static_cast< CoreImpl * >( core ); // Jeśli nie chcesz tutaj static_cast'a, to destruktor Core musi być wirtualny
}

Z punktu widzenia użytkownika:


#include "Core.h"

int main()
{
  Core *core = CreateCore();

  core->SomeMethod();

  DestroyCore( core );
}

Użytkownik dostaje tylko zawartość Public/ i .lib, nie zna zawartości Private/.

Można też to robić inaczej, gdzie kod C++ jest wrapperem tylko na kod C. Polecam zajrzeć do tego jak jest zrobiony Discord SDK (https://discord.com/developers/docs/game-sdk/sdk-starter-guide ) (download: https://dl-game-sdk.discordapp.net/2.5.6/discord_game_sdk.zip)

1

Drugim sposobem, żeby to zrobić jest ukrycie zależności pod wskaźnikiem (tak to robi na przykład Qt):

Drzewo plików:

Public/Core.h
Private/CoreImpl.h
Private/Point.h
Private/Triangle.h
Private/CoreImpl.cpp
// Public/Core.h
#pragma once

class Core
{
public:
  Core();
  ~Core();

  void SomeMethod();

private:
  class Impl;
  Impl *m_impl;
};
// Private/Point.h
#pragma once

class Point
{
};
// Private/Triangle.h
#pragma once

class Triangle
{
};
// Private/CoreImpl.h
#pragma once

#include "Core.h"
#include "Point.h"
#include "Triangle.h"

class Core::Impl
{
public:
  void SomeMethod();

private:
  Point m_point;
  Triangle m_triangle;
};
// Private/CoreImpl.cpp
#include "CoreImpl.h"

Core::Core()
{
  m_impl = new Impl;
}

Core::~Core()
{
  delete m_impl;
}

void Core::SomeMethod()
{
  return m_impl->SomeMethod();
}

void Core::Impl::SomeMethod()
{
  // Implementacja funkcji Core::SomeMethod()
}

Z punktu widzenia użytkownika:

#include "Core.h"

int main()
{
  Core core;
  core.SomeMethod();
}
0

@Crow:

Headery schodzące do każdej klasy, to chyba pomyliłeś języki.
Może jak w laboratorium robimy proof of concept gdy dynamicznie żyje, ale już nigdy potem w kodzie udostępnionym.
.
najbardziej optymistyczny podział headerów, 1 plik ~= coś a'la pakiet / moduł

ps. nie byłbym sobą, gdybym nie dodał, że sama koncepcja include sięga lat 1950ch i jest od pięć pięter bardziej prymitywna od importu (tak to się nazywa w większości, using, use itd )

1 użytkowników online, w tym zalogowanych: 0, gości: 1