Informatyk jest w życiu uczony lenistwa – po co robić coś co dobrze zrobił już ktoś inny, czyli używamy bibliotek.
Niestety mimo iż C++ jest bardzo popularny, a wg Tiobe-index jest to III najpopularniejszy język programowania, oraz mimo dużej społeczności, prężnego rozwoju standardów co trzy lata wciąż istnieje problem z instalacją dodatkowych bibliotek programistycznych. Pojawiło się wiele narzędzi, które to umożliwiają, np. wieloplatformowy manager pakietów Conan, a także wiele innych. Jednakże jeden z nich wg mnie zasługuję na szczególną uwagę: vcpkg, gdyż jest bardzo wygodny w użyciu, opensourcowy, oraz zawiera dużo bibliotek (nie tylko twórcy mogą dorzucić tam bindingi do bibliotek, ale każdy, przez co baza jest olbrzymia), a także jest wieloplatformowy (wszędzie się go łatwo używa, ale na Windowsie dla środowiska Microsoft Visual Studio jeszcze łatwiej).
Dużo z teorii na temat bibliotek niestandardowych opisałem w innym moim wpisie, więc tutaj, żeby się nie powtarzać, od razu polecimy z koksem.
Instalacja biblioteki
Mamy projekt, w którym chcemy skorzystać z biblioteki, która zawiera zarówno nagłówki, jak i źródła. Aby jej użyć musimy wykonać następujące kroki:
- mieć zbudowany VCPKG
- zawołać
vcpkg install $MY_LIBRARY
np. boost-program-options
(do parsowania argumentów uruchomienia programu), czyli będzie to: vcpkg install boost-program-options
- podpiąć do naszego projektu, najwygodniej przez CMake, ale o tym później.
Instalacja na konkretnym przykładzie
Załóżmy, że chcemy odpalić przykład użycia boost-program-option
, z oficjalnej strony tutoriala pozwolę sobie przytoczyć ten kod:
// Copyright Vladimir Prus 2002-2004.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
/* The simplest usage of the library.
*/
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <iostream>
#include <iterator>
using namespace std;
int main(int ac, char* av[])
{
try {
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("compression", po::value<double>(), "set compression level")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
if (vm.count("help")) {
cout << desc << "\n";
return 0;
}
if (vm.count("compression")) {
cout << "Compression level was set to "
<< vm["compression"].as<double>() << ".\n";
} else {
cout << "Compression level was not set.\n";
}
}
catch(exception& e) {
cerr << "error: " << e.what() << "\n";
return 1;
}
catch(...) {
cerr << "Exception of unknown type!\n";
}
return 0;
}
Powyższy kod będzie w pliku main.cpp
. Wszystkie poniższe kroki wykonam w tym samym katalogu (chyba, że jawnie napiszę inaczej):
Następnym krokiem jest zbudowanie vcpkg, które wpierw ściągamy z oficjalnego repozytorium, ja to robię na Linuxie tak:
git clone https://github.com/Microsoft/vcpkg.git --depth=1
Następnie wołamy (niezależnie od systemu operacyjnego): ./vcpkg/bootstrap-vcpkg.sh
, wtedy w katalogu gdzie ściągnęliśmy repozytorium powstanie plik wykonywalny vcpkg
lub vcpkg.exe
. To wszystko, mamy już VCPKG, które teraz możemy albo zostawić tam, gdzie jest (czyli w katalogu projektu), albo przenieść w jakieś bardziej ogólne miejsce w systemie.
Następnie wołamy ./vcpkg/vcpkg install boost-program-options
(na Windowsie ./vcpkg/vcpkg.exe ...
), po chwili mamy już bibliotekę zbudowaną w naszym systemie. Na końcu outputu widzimy od razu prawie gotowy kod, który powinniśmy dorzucić do pliku CMakeLists.txt
naszego projektu. W powyższym przypadku u mnie wygląda to tak:
Stored binaries in 1 destinations in 698 ms.
Elapsed time to handle boost-program-options:x64-linux: 26 s
Total install time: 1.9 min
The package boost is compatible with built-in CMake targets:
find_package(Boost REQUIRED [COMPONENTS <libs>...])
target_link_libraries(main PRIVATE Boost::boost Boost::<lib1> Boost::<lib2> ...)
Ale dodać powyższe musimy tylko jeśli używamy CMake’a, bo pliki znajdują się w katalogu ./vcpkg/installed/.../{include,lib,share}
, więc można je dodać do projektu. Przykładowo dla powyższej biblioteki plik jesteśmy w stanie skompilować komendą (na Linuxie, ale analogiczna będzie na Windowsie):
g++ -isystem./vcpkg/installed/x64-linux/include/ main.cpp -L./vcpkg/installed/x64-linux/lib/ -lboost_program_options -o program
.
Wiadomo, że przy większych projektach nie bawimy się bezpośrednio komendami, tylko dopinamy to do projektu w ulubionym środowisku programistycznym. Ale właśnie zaletą VCPKG jest, że nie musimy tego robić ręcznie! VCPKG ma wsparcie pod Visual Studio (nie mylić z Visual Studio Code), oraz pod CMake’a.
Na Windowsie
vcpkg jest autorstwa Microsoftu, więc zadbali, aby to dobrze działało.
Visual Studio (nie Visual Studio Code)
Wtedy wystarczy zawołać:
.\vcpkg integrate install
i będziemy mieli nasze biblioteki dostępne we WSZYSTKICH PROJEKTACH, które kodujemy w Visual Studio Community/Proffesional. To wszystko, tak bardzo wygodne.
Inny kompilator na Windowsie
Wg StackOverflow (https://stackoverflow.com/questions/63031513/vcpkg-with-mingw) jest to możliwe aby użyć innego kompilarora – MinGW. W tym celu instalując bibiliotekę musimy podmienić „triplet” np.: ./vcpkg install boost-program-options --triplet=x64-mingw-dynamic
. Następnie możemy podpiąć ręcznie, w formie: g++ -isystem./vcpkg/installed/x64-linux/include/ main.cpp -L./vcpkg/installed/x64-linux/lib/ -lboost_program_options -o program
, lub bazować na CMake’u.
Linux i inne systemy, które używają CMake’a
Zacznijmy od utworzenia pliku CMakeLists.txt o treści przykładowo:
cmake_minimum_required(VERSION 3.20)
project(Nauka_Vcpkg)
add_executable(${PROJECT_NAME} main.cpp)
Następnie możemy ten plik załadować przez środowisko programistyczne, ewentualnie użyć komend (Linux):
cmake . # to nam wygeneruje konfiguracje do Makefile'a
make # odpalenia budowania przez program Makefile
- Uruchomienie programu, w powyższym przypadku będzie to
./Nauka_Vcpkg
.
Niestety program się nam nie skompiluje, musimy dodać integracje wyplutą wcześniej przez vcpkg po zawołaniu vcpkg install ...
.
Zaktualizujmy więc CMakeLists.txt o, wypluty przez vcpkg, kod z modyfikacjami:
cmake_minimum_required(VERSION 3.20)
project(Learning_VCPKG LANGUAGES CXX)
find_package(Boost COMPONENTS program_options REQUIRED)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::boost Boost::program_options)
To wszystko! Kod się kompiluje i linkuje!
Ważna uwaga – jeśli instalujemy komponenty biblioteki to stosujemy podkreślenie dolne _ a nie pauzę -.
Zasadniczo tyle by nam wystarczyło, ale w przypadku biblioteki boost jeśli nie podamy wersji to pojawi się warning, który niwelujemy przez dodanie set(Boost_NO_WARN_NEW_VERSIONS True)
przed komendą find_package(Boost ...
.
___________________
Większe potrzeby – instalacja biblioteki z zależnościami
Kiedyś napisałem artykuł o wieloplatformowej bibliotece do baz danych SOCI. W artykule bazą danych jest sqlite3. Można też użyć biblioteki boost dla wygody programowania. Czyli mamy trzy biblioteki do zainstalowania i ogarnięcia zależności. Na szczęście z VCPKG nie jest to trudne, wpierw sprawdźmy, czy nasza biblioteka jest, oraz w jakich wariantach:
$ ./vcpkg/vcpkg search soci
soci 4.0.3#3 SOCI – The C++ Database Access Library
soci[boost] Integration with Boost
soci[empty] Build the backend skeleton for new backends development
soci[mysql] Build mysql backend
soci[odbc] Build odbc backend
soci[postgresql] Build postgresql backend
soci[sqlite3] Build sqlite3 backend
Jak widać mamy to czego szukamy, a opcje można ze sobą łączyć, przykładowo:
./vcpkg/vcpkg install ‚soci[sqlite3,boost]’
W odpowiedzi pojawi się nam tekst:
find_package(SOCI CONFIG REQUIRED)
# Using core (loading backends at runtime)
target_link_libraries(main PRIVATE $,SOCI::soci_core,SOCI::soci_core_static>)
# Using the sqlite3 backend directly
target_link_libraries(main PRIVATE $,SOCI::soci_sqlite3,SOCI::soci_sqlite3_static>)
Który dodajemy do naszego pliku CMakeLists.txt i działamy można użyć przykładu z artykułu, np.:
#include <iostream>
#include <string>
#include <chrono>
#include <exception>
#include <soci/soci.h>
#include <soci/sqlite3/soci-sqlite3.h>
using namespace soci;
using namespace std;
using namespace std::chrono;
void runMultipleQueriesWithVariables( soci::session & sql, size_t queriesCount )
{
string name;
int age;
double salary;
for( size_t i = 0; i < queriesCount; ++i )
{
name = "Janusz " + to_string( i );
age = rand() % 100;
salary = static_cast < double >( rand() ) / RAND_MAX * 10000;
sql << "INSERT INTO Employers(ID, Name, Age, Salary) VALUES (:id, :name, :age, :salary); ", use( i ), use( name ), use( age ), use( salary );
}
}
void runMultipleQueriesWithStreams( soci::session & sql, size_t queriesCount )
{
string name;
int age;
double salary;
for( size_t i = 0; i < queriesCount; ++i )
{
name = "Janusz " + to_string( i );
age = rand() % 100;
salary = static_cast < double >( rand() ) / RAND_MAX * 10000;
sql << "INSERT INTO Employers(ID, Name, Age, Salary) VALUES (" << i << ", '" << name << "'," << age << "," << salary << ");";
}
}
void runMultiplePreparedQueries( soci::session & sql, size_t queriesCount )
{
string name;
int age, i;
double salary;
statement st =( sql.prepare << "INSERT INTO Employers(ID, Name, Age, Salary) VALUES (:id, :name, :age, :salary);",
use( i ), use( name ), use( age ), use( salary ) );
for( i = 0; i < queriesCount; ++i )
{
constexpr bool exhangeData = true;
st.execute( exhangeData );
}
}
void testFunctionExecutionTime( const char * textPrefix, void( * function )( soci::session &, size_t ), soci::session & sql )
{
auto startingTime = steady_clock::now();
{
constexpr size_t queriesCount = 1000;
function( sql, queriesCount );
}
const auto elapsedTimeInSeconds = std::chrono::duration < double >( steady_clock::now() - startingTime ).count();
cout << textPrefix << ", the function took seconds: " << elapsedTimeInSeconds << endl;
sql << "DELETE FROM Employers;";
}
int main()
{
try
{
soci::session sql( sqlite3, "Employers3.db" );
sql << "CREATE TABLE IF NOT EXISTS Employers("
"ID INT PRIMARY KEY NOT NULL,"
"Name TEXT NOT NULL,"
"Age INT NOT NULL,"
"Salary REAL);";
testFunctionExecutionTime( "not prepared with variables", runMultipleQueriesWithVariables, sql );
testFunctionExecutionTime( "not prepared with streams", runMultipleQueriesWithStreams, sql );
testFunctionExecutionTime( "prepared query", runMultiplePreparedQueries, sql );
sql << "DELETE TABLE Employers;";
}
catch( const exception & e )
{
cerr << "Error: " << e.what() << '\n';
}
}
A plik CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(LearningVcpkg LANGUAGES CXX)
add_executable(${PROJECT_NAME} main_boost.cpp)
find_package(SOCI CONFIG REQUIRED)
# Using core (loading backends at runtime)
target_link_libraries(${PROJECT_NAME} PRIVATE $<IF:$<TARGET_EXISTS:SOCI::soci_core>,SOCI::soci_core,SOCI::soci_core_static>)
# Using the sqlite3 backend directly
target_link_libraries(${PROJECT_NAME} PRIVATE $<IF:$<TARGET_EXISTS:SOCI::soci_sqlite3>,SOCI::soci_sqlite3,SOCI::soci_sqlite3_static>)
Linki/bibliografia:
– Jak zacząć z VCPKG