microsoft/proxy

repository·main·Indexed 25 days ago

https://github.com/microsoft/proxy

A C++20 library providing a high-performance, non-intrusive alternative to traditional inheritance-based polymorphism. It enables polymorphic behavior using pointers and composition, featuring GC-like lifetime management and support for freestanding environments. The library utilizes a facade-based system via `basic_facade_builder` to configure conventions, skills, and constraints, including support for operator dispatching and type conversions.

Tokens
45.1K
Snippets
83
Records
152
Agent score
84%

What's inside microsoft-proxy

  1. Overview of Proxy: Next Generation Polymorphism in C++

    main

    Proxy is a modern C++20 header-only library designed to enable polymorphism without the need for traditional inheritance. It provides a way to use different types of objects interchangeably using pointers as a foundation, similar to how traits work in languages like Rust.

    Key features include:

    • Portable: Implemented in standard C++20 and largely freestanding, making it suitable for embedded systems or kernel design.
    • Non-intrusive: Implementation types do not need to inherit from an abstract binding.
    • Well-managed: Provides GC-like lifetime management for objects without a full garbage collector.
    • Fast: Designed to produce high-quality code that often outperforms traditional inheritance-based approaches.
    • Flexible: Supports polymorphism for member functions, free functions, operators, and conversions.

    Note: This repository is no longer actively maintained by Microsoft. Active development has moved to the ngcpp/proxy organization.

  2. Use the `proxy` class template

    main

    The proxy<F> class template is a general-purpose polymorphic wrapper for C++ objects based on pointer semantics. It allows for flexible lifetime management without runtime garbage collection.

    Key characteristics:

    • Memory Management: If a proxy<F> contains a value, the value is guaranteed to be allocated within the proxy object's footprint (no dynamic memory allocation occurs during storage), provided the pointer type P satisfies the proxiable<P, F> requirement.
    • Extensibility: Unlike std::function or std::any, proxy allows you to define custom runtime abstraction requirements via a facade.
    • Custom Allocators: Fully supports custom allocators via allocate_proxy.

    To use it, include proxy.h and ensure your facade is defined using facade_builder or similar mechanisms.

    template <facade F>
    class proxy;
  3. Namespace and Versioning in Proxy 4

    main

    Proxy 4 uses inline namespaces to support side-by-side installations of different major versions.

    • All APIs are defined in the pro namespace.
    • Version 4 is exported as an inline namespace. Unqualified names (e.g., pro::foo) resolve to pro::v4::foo.
    • If you need to reference a different major version in the same translation unit, use the explicit namespace (e.g., pro::v3::foo).
  4. Install Proxy via CMake/CPM

    main

    You can integrate the Proxy library into your CMake project using CPM or FetchContent_Declare. Ensure you use version 4.0.0 or above.

    CPMAddPackage(
      NAME msft_proxy4
      GIT_TAG 4.0.0 # or above
      GIT_REPOSITORY https://github.com/microsoft/proxy.git
    )
    
    target_link_libraries(main PRIVATE msft_proxy4::proxy)
  5. Migrate from virtual functions to Proxy

    main

    To upgrade an existing project from inheritance-based polymorphism (virtual functions) to Proxy, follow these four steps:

    1. Update Compiler: Ensure your compiler version meets the minimum requirements.
    2. Define Facades: Create facade types that correspond to your existing virtual base classes.
    3. Replace Boundaries: Replace the usage of virtual base classes with proxy types at your API boundaries.
    4. Cleanup: Remove the original definitions and inheritance hierarchies of the virtual base classes.
  6. Compose complex skillsets using `add_skill`

    main

    You can create complex skillsets by defining an alias template that combines multiple member alias templates from basic_facade_builder. This allows you to group multiple skills (like layout restrictions, copy support, and specific semantic skills) into a single reusable component that can then be applied via add_skill.

    #include <proxy/proxy.h>
    
    // Define a composite skill by chaining builder aliases
    template <class FB>
    using SharedSlim = typename FB
        ::template restrict_layout<sizeof(void*), alignof(void*)>
        ::template support_copy<pro::constraint_level::nothrow>
        ::template add_skill<pro::skills::as_weak>;
    
    // Apply the composite skill to a new facade
    struct SharedFormattable : pro::facade_builder
                               ::add_skill<SharedSlim>
                               ::add_skill<pro::skills::format>
                               ::build {};
  7. Configure CMake to use Proxy C++ Modules

    main

    Since version 4.0.0, Proxy ships with .ixx files. Due to current CMake limitations regarding forward compatibility with C++ modules, you must manually declare the msft_proxy4::proxy_module target if you are consuming the library.

    Note: The C++ standard version for the msft_proxy4_module target must be the same or higher than your consumer CMake target. For example, if your project uses C++23, change cxx_std_20 to cxx_std_23 in the script below.

    ```cmake
    find_package(msft_proxy4 REQUIRED)
    
    if(NOT DEFINED msft_proxy4_INCLUDE_DIR) # (1)
      if(NOT DEFINED msft_proxy4_SOURCE_DIR)
        message(FATAL_ERROR "`msft_proxy4_INCLUDE_DIR` or `msft_proxy4_SOURCE_DIR` must be defined to use this script.")
      endif()
      set(msft_proxy4_INCLUDE_DIR ${msft_proxy4_SOURCE_DIR}/include)
    endif()
    
    message(STATUS "Declaring `msft_proxy4::proxy_module` target for include path `${msft_proxy4_INCLUDE_DIR}`")
    
    add_library(msft_proxy4_module)
    set_target_properties(
      msft_proxy4_module
      PROPERTIES
        SYSTEM TRUE
        EXCLUDE_FROM_ALL TRUE
    )
    
    add_library(msft_proxy4::proxy_module ALIAS msft_proxy4_module)
    target_sources(msft_proxy4_module PUBLIC
      FILE_SET CXX_MODULES
      BASE_DIRS ${msft_proxy4_INCLUDE_DIR}
      FILES
        ${msft_proxy4_INCLUDE_DIR}/proxy/v4/proxy.ixx
    )
    target_compile_features(msft_proxy4_module PUBLIC cxx_std_20) # (2)
    target_link_libraries(msft_proxy4_module PUBLIC msft_proxy4::proxy)

    To consume the module in your application:

    target_link_libraries(main PRIVATE msft_proxy4::proxy_module)

    Note (1): msft_proxy4_INCLUDE_DIR is automatically declared after find_package(msft_proxy4). If using CPM, msft_proxy4_SOURCE_DIR is declared after CPMAddPackage.

  8. Integrate Proxy into your project

    main

    Proxy is a header-only, cross-platform C++20 library. You can integrate it using one of the following methods:

    1. Manual Installation: Download the source code from the latest release on GitHub and include proxy.h in your project.
    2. Package Managers: Search for and install proxy via vcpkg or conan.

    Ensure your compiler meets the minimum requirements specified in the project documentation.

  9. Upgrade Proxy in large codebases

    main

    When upgrading Proxy in a monorepo or multi-module product, follow these versioning rules to avoid ODR violations:

    Minor or Patch Upgrades (e.g., 3.3.0 → 3.4.0)

    All 3.x.y releases preserve API/ABI compatibility. Different parts of your program can safely depend on different 3.x.y versions.

    Major Upgrades (e.g., 3.4.0 → 4.0.0)

    1. Intermediate Step: If your current version is earlier than 3.4.0, migrate to 3.4.0 first.
    2. Versioned Namespaces: Starting from 3.4.0, each major release uses a versioned inline namespace (e.g., pro::v3, pro::v4).
    3. Explicit Qualification: If a translation unit includes headers from multiple major versions, qualify the namespace explicitly:
      pro::v3::foo(); // Proxy 3 API
      pro::v4::foo(); // Proxy 4 API
    4. Macro Aliases: Use major-qualified macro aliases (e.g., PRO4_DEF_MEM_DISPATCH) when multiple major versions are present in the same translation unit.
    5. Incremental Rollout: Upgrade subsystems incrementally (module-by-module or DLL-by-DLL). Once a target is fully migrated, you can use unqualified calls (e.g., pro::foo()) to use the latest version and remove the old version.
    pro::v3::foo(); // Proxy 3 API
    pro::v4::foo(); // Proxy 4 API
  10. Build and Run Tests with CMake

    main

    To build the Proxy library and run its test suite using CMake, follow these steps:

    1. Clone the repository.
    2. Configure the build directory.
    3. Build the project.
    4. Execute the tests using ctest.
    git clone https://github.com/microsoft/proxy.git
    cd proxy
    cmake -B build
    cmake --build build -j
    ctest --test-dir build -j
  11. Specialize `is_bitwise_trivially_relocatable` for custom types

    main

    You can opt-in your own types to the fast relocation path by providing a specialization of is_bitwise_trivially_relocatable<T> in the pro namespace.

    To do this correctly, ensure the type does not depend on its address remaining stable (e.g., it must not use self-pointers, intrusive container hooks, or rely on pointer provenance).

  12. Create a custom polymorphic abstraction with `proxy`

    main

    To create a custom abstraction, use facade_builder to define a facade. This allows you to specify conventions (member functions) and reflections that the proxy will expose.

    1. Define a dispatch macro using PRO_DEF_MEM_DISPATCH.
    2. Use pro::facade_builder to add conventions.
    3. Pass the resulting facade to pro::proxy<YourFacade>.
    4. Use operator-> to access the abstracted members.
    #include <iostream>
    #include <map>
    #include <memory>
    #include <string>
    #include <vector>
    #include <proxy/proxy.h>
    
    // 1. Define the dispatch for a member function
    PRO_DEF_MEM_DISPATCH(MemAt, at);
    
    // 2. Build the facade
    struct Dictionary : pro::facade_builder                       
                        ::add_convention<MemAt, std::string(int)> 
                        ::build {};
    
    // 3. Use the proxy in a function
    void PrintDictionary(pro::proxy<Dictionary> dictionary) {
      std::cout << dictionary->at(1) << "\n";
    }
    
    int main() {
      static std::map<int, std::string> container1{{1, "hello"}};
      auto container2 = std::make_shared<std::vector<const char*>>();
      container2->push_back("hello");
      container2->push_back("world");
    
      // 4. Pass pointers to the proxy
      PrintDictionary(&container1); // Prints "hello"
      PrintDictionary(container2);  // Prints "world"
    }