Skip to main content

    Lesson • Intermediate

    Creating & Using C++ Libraries

    By the end of this lesson you'll know the difference between the standard library and third-party code, when to reach for static vs dynamic vs header-only libraries, what the -I/-L/-l flags actually do, and how a package manager like vcpkg or Conan saves you from wiring all of it up by hand.

    What You'll Learn

    • Tell the standard library apart from third-party libraries
    • Choose between static (.a/.lib) and dynamic (.so/.dll/.dylib) libraries
    • Use header-only libraries with nothing but #include
    • Link libraries correctly with -I, -L and -l
    • Install dependencies with package managers (vcpkg, Conan)
    • Consume any library: include its header, then link its code

    💡 Real-World Analogy

    Think of building a program like assembling flat-pack furniture. The header is the instruction sheet — it promises "a function called area() exists and takes a radius". The library is the box of actual parts — the compiled code that does the work. The compiler reads the instructions; the linker bolts in the parts. A static library is parts you glue permanently inside the finished piece (heavier, but nothing can fall off). A dynamic library is parts you keep in a shared drawer that several pieces of furniture borrow at use-time (lighter, but the drawer had better still be there). When you "#include the header but forget to link the library", you've read the instructions but never opened the box — hence the famous undefined reference error.

    📊 The Four Kinds of Library

    KindWhere the code livesFilesLink step?
    standardShips with the compiler<iostream>…Automatic
    header-onlyAll in the header.hpp / .hNone
    staticCopied into your exe.a / .libAt build time
    dynamicSeparate file, loaded at run time.so / .dll / .dylibBuild + run time

    A library is just reusable compiled code plus a header that describes it. The header tells the compiler what exists; the library provides the actual instructions the linker bolts in.

    1. Standard Library vs Third-Party

    Every header you've used so far — <iostream>, <vector>, <string>, <algorithm> — is part of the standard library. It ships with the compiler, so you only #include it and it just works. A third-party library is code someone else wrote and published; you have to obtain it, tell the compiler where its headers are, and (usually) link its compiled code. Read this worked example, run it, and notice that everything here is "free" — no extra build flags needed.

    Worked example: the standard library is free

    Read every comment, run it, and check the output matches.

    Try it Yourself »
    C++
    #include <iostream>   // standard library: input/output
    #include <vector>     // standard library: a growable array
    #include <string>     // standard library: text
    #include <algorithm>  // standard library: sort, max, min...
    using namespace std;
    
    // The STANDARD library ships with every compiler. You only
    // #include it — no download, no extra link flags.
    //
    // A THIRD-PARTY library would look like:
    //     #include <nlohmann/json.hpp>   // you must install it first
    // and might need a link flag 
    ...

    2. Header-Only Libraries

    The simplest kind of third-party library is header-only: all of its code lives in the header file, so there is nothing to compile or link separately — you #include it and build. This is why templates and inline functions live in headers, and why libraries like nlohmann/json, Catch2, and fmt are so easy to adopt. The one rule: mark non-template functions inline so including the header in several .cpp files doesn't cause "multiple definition" errors.

    Worked example: a header-only utility

    Just #include and use — no link step.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    // HEADER-ONLY library: all the code lives in the header, so you
    // just #include it. No separate compile step, no link flag.
    // Real examples: nlohmann/json, Catch2, fmt (header mode).
    //
    // Imagine this block is the file  math_utils.hpp  that you
    // would normally pull in with  #include "math_utils.hpp"
    namespace mathlib {
        // 'inline' lets a function live in a header without causing a
        // 
    ...

    Your turn. The namespace below is pretending to be a header-only file. Fill in the two blanks marked ___ using the hints in the comments, then run it.

    🎯 Your turn: write a header-only helper

    Fill in the ___ blanks, then check your output against the expected line.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    #include <algorithm>
    using namespace std;
    
    // 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
    // Pretend this namespace is your header-only file  text_utils.hpp
    
    namespace textlib {
        // 1) Mark this helper 'inline' so it is safe in a header
        ___ string shout(string s) {        // 👉 the keyword is  inline
            transform(s.begin(), s.end(), s.begin(), ::toupper);
            return s + "!";
        }
    }
    
    int main() {
        // 2) Call your header-o
    ...

    3. Static Libraries (.a / .lib)

    A static library is an archive of pre-compiled object files — .a on Linux/macOS, .lib on Windows. At link time the linker copies the bits you actually use straight into your executable. The upside: the program is self-contained, with no runtime dependency to ship. The downside: a bigger binary, and you must rebuild to pick up a library update. The worked example below runs real geometry code and shows, in comments, the exact g++ and CMake commands you'd use to build and link it.

    Worked example: static library + build commands

    Runnable geometry code with the ar/g++/CMake recipe in comments.

    Try it Yourself »
    C++
    #include <iostream>
    #include <cmath>
    using namespace std;
    
    // STATIC library (.a on Linux/macOS, .lib on Windows).
    // The linker copies the library's code straight INTO your .exe,
    // so the program is self-contained but larger.
    //
    // If this namespace lived in geometry.cpp, you would build it like:
    //   1) compile to an object file:   g++ -c geometry.cpp -o geometry.o
    //   2) archive into a static lib:   ar rcs libgeometry.a geometry.o
    //   3) link it into your program:   g++ main.cpp -L. -lgeom
    ...

    4. Dynamic Libraries & the Linking Flags

    A dynamic (or shared) library — .so on Linux, .dll on Windows, .dylib on macOS — stays a separate file that is loaded when your program runs. Your executable stays small and many programs can share one copy, but that file must be found at run time or the program won't start. Linking any non-header library comes down to three flags: -I<dir> says where the headers are, -L<dir> says where the library files are, and -l<name> says which library to link (-lcurl links libcurl.so — drop the lib prefix and the extension).

    Worked example: shared library + -I/-L/-l

    See the build recipe and what each linking flag does.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    using namespace std;
    
    // DYNAMIC / SHARED library (.so Linux, .dll Windows, .dylib macOS).
    // It stays a SEPARATE file, loaded when the program runs. The .exe
    // is small, and many programs can share one copy — but the file
    // must be found at run time or you get a "cannot open shared object".
    //
    // Build a shared lib and link against it:
    //   g++ -shared -fPIC logger.cpp -o liblogger.so   // -fPIC = position-independent code
    //   g++ main.cpp -L. -llogger -
    ...

    Now you try. The blanks below aren't C++ — they're the build flags. Fill in the right flag for the library folder and the library name so the recipe would actually find and link libcurl:

    5. Package Managers & Consuming a Library

    Hand-writing -I/-L/-l for one library is fine; doing it for a dozen libraries that each have their own dependencies is miserable. A package managervcpkg (Microsoft) or Conan — downloads a library, builds it for your platform, and wires it into your build (typically via CMake's find_package). Whatever the tooling, consuming a library is always the same two steps: include its header, then link its compiled code. The example shows the vcpkg and Conan recipes in comments around runnable code.

    Worked example: vcpkg / Conan + the two consume steps

    See how a package manager replaces manual link flags.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    using namespace std;
    
    // PACKAGE MANAGERS install third-party libraries for you and wire
    // them into your build, so you stop hand-writing -I/-L/-l flags.
    //
    // vcpkg:
    //   vcpkg install fmt
    //   (CMake)  find_package(fmt CONFIG REQUIRED)
    //            target_link_libraries(app PRIVATE fmt::fmt)
    //
    // Conan:
    //   conanfile.txt ->  [requires]  fmt/10.2.1
    //   conan install .
    //
    // To CONSUME any library you always do the same two things:
    //   1) INCLUDE its h
    ...

    🔎 Deep Dive: static vs dynamic — which do I pick?

    Reach for static when you want a single self-contained binary you can copy anywhere — command-line tools and containers love this. Reach for dynamic when several programs share one library, when the library is large, or when you want to patch the library (a security fix) without rebuilding every program that uses it.

    Header-only wins when the library is small or template-heavy and you value "just drop it in". The trade-off is slower compiles, because the code is recompiled in every translation unit that includes it.

    # static  -> baked into the exe, no runtime file needed
    g++ main.cpp -L. -lgeometry -o app
    
    # dynamic -> exe stays small, liblogger.so must exist at run time
    g++ main.cpp -L. -llogger -o app
    LD_LIBRARY_PATH=. ./app   # so the loader can find liblogger.so

    Pro Tips

    • 💡 Link order matters with g++: put -l flags after the files that use them — g++ main.cpp -lfoo, not g++ -lfoo main.cpp.
    • 💡 -l drops the lib and the extension: -lcurl links libcurl.so or libcurl.a.
    • 💡 Header-only? mark functions inline: it prevents "multiple definition" errors when several files include the header.
    • 💡 Prefer a package manager once you have 2+ deps: vcpkg/Conan handle transitive dependencies and versions you'd otherwise track by hand.

    Common Errors (and the fix)

    • "undefined reference to `foo'" (a LINK error): the header was found and compiled, but the function's actual code was never linked in. Add the library to the link step: g++ main.cpp -L. -lfoo -o app.
    • "fatal error: foo.h: No such file or directory": the compiler can't find the header. Point it at the include folder with -I/path/to/include.
    • "cannot find -lfoo": the linker can't find the library file. Add its folder with -L/path/to/lib, and check the file is named libfoo.a / libfoo.so.
    • "error while loading shared libraries: libfoo.so: cannot open shared object file": the dynamic library isn't on the run-time search path. Set LD_LIBRARY_PATH (Linux) or place the .dll next to the exe (Windows).
    • ABI / version mismatch (crashes or garbage at run time): your code was built against one version of a library's headers but linked or loaded a different version (different compiler, flags, or release). Rebuild everything with the same compiler and the matching library version.

    📋 Quick Reference: Static vs Dynamic

    AspectStaticDynamic / Shared
    Extension.a / .lib.so / .dll / .dylib
    Code goesInto the executableStays a separate file
    Binary sizeLargerSmaller
    Needed at run timeNoYes (file must be found)
    Update the libraryRebuild the programSwap the file, no rebuild
    Build flag-L. -lfoo-L. -lfoo
    Build itar rcs libfoo.a foo.og++ -shared -fPIC

    Frequently Asked Questions

    Q: What is the difference between the standard library and a third-party library?

    The standard library (the STL plus things like <iostream>, <vector>, <string>) ships with every C++ compiler, so you only need to #include it. A third-party library is written by someone else — you have to download it, tell the compiler where its headers are, and usually link against its compiled code with -l.

    Q: What is the difference between a static and a dynamic (shared) library?

    A static library (.a on Linux/macOS, .lib on Windows) is copied INTO your executable at link time, so the program is self-contained but bigger. A dynamic/shared library (.so, .dll, .dylib) stays a separate file that is loaded when the program runs, so the executable is smaller and many programs can share one copy — but that file must be present at run time.

    Q: Why do I get 'undefined reference' when my header includes compile fine?

    Including a header only tells the compiler the function exists; it does not provide the function's body. 'undefined reference' is a LINKER error meaning the implementation was never linked in. Add the library to the link step with -lname and point the linker at its folder with -Lpath.

    Q: What is a header-only library and why is it so popular?

    A header-only library puts all of its code (usually templates and inline functions) inside header files, so there is nothing to compile or link separately — you just #include it and build. nlohmann/json, Catch2, and fmt (in header mode) work this way, which makes them very easy to drop into a project.

    Q: Do I really need a package manager like vcpkg or Conan?

    Not for a single header-only library — you can just copy the header in. But once you depend on several libraries with their own dependencies and version requirements, a package manager (vcpkg, Conan) downloads, builds, and wires them into your build for you, which saves a lot of manual -I/-L/-l bookkeeping.

    Mini-Challenge: Your Own Header-Only Toolkit

    No blanks this time — just a brief and a blank canvas (with an outline to keep you on track). Build a tiny header-only string library, run it, and check your output against the example in the comments. This is exactly how real header-only libraries start.

    🎯 Mini-Challenge: build a header-only toolkit

    Write the inline helpers yourself and print the results.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    // 🎯 MINI-CHALLENGE: your own header-only string toolkit
    // 1. Above main, make a namespace  strkit  with TWO inline helpers:
    //      - inline string repeat(const string& s, int n)  -> s joined n times
    //      - inline bool   isBlank(const string& s)         -> true if s is empty
    // 2. In main(), call both and print the results.
    // 3. BONUS: add an inline helper  surround(s, edge) that returns
    //      edge + s + edge 
    ...

    🎉 Lesson Complete

    • ✅ The standard library ships with the compiler; third-party code you obtain and link yourself
    • Header-only libraries need only #include (mark functions inline)
    • Static (.a/.lib) is copied into the exe; dynamic (.so/.dll/.dylib) loads at run time
    • ✅ Link with -I (headers), -L (library folder), -l (which library)
    • undefined reference means "found the header, didn't link the code"
    • vcpkg and Conan install and wire up dependencies for you
    • Next lesson: Final Project — put your C++ skills together

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service