c++pointerslibstdc++

Kind of weak pointer for unique_ptr?


#include <memory>
#include <iostream>

class A
{
public:
    int i = 8;
};

class B {
public:
    std::unique_ptr<A> p;
};

int main()
{
    B b;
    b.p.reset(new A);
    std::cout << "Out1: " << b.p->i << std::endl;

    const A* const a = b.p.get();

    delete a; // how to forbid this statement?

    std::cout << "Out2: " << b.p->i << std::endl;
}

is nonsense and yields nonsense:

Out1: 8
Out2: 1587634531
free(): double free detected in tcache 2
Aborted

How to detect such nonsense at compile time, i.e. how to declare pointer a such that it has r/w access to *a (of type A), but cannot corrupt the unique pointer B.p?

The const around A* are not the solution.

In the case of std::shared_ptr, we have std::weak_ptr - do we have nothing alike for std::unique_ptr?


Solution

  • Per the comments, you can use a static analysis tool to forbid new/delete, setting warnings as errors, and running on pull requests on CICD. In particular, running clang-tidy on your code gives:

    [build] main.cpp:19:9: error: use std::make_unique instead [modernize-make-unique,-warnings-as-errors]
    [build]     b.p.reset(new A);
    [build]        ~^~~~~ ~~~~~
    [build]         = std::make_unique<A>
    [build] main.cpp:19:15: error: initializing non-owner argument of type 'pointer' (aka 'A *') with a newly created 'gsl::owner<>' [cppcoreguidelines-owning-memory,-warnings-as-errors]
    [build]     b.p.reset(new A);
    [build]               ^~~~~
    [build] main.cpp:24:5: error: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead [cppcoreguidelines-owning-memory,-warnings-as-errors]
    [build]     delete a; // how to forbid this statement?
    [build]     ^      ~
    [build] main.cpp:22:5: note: variable declared here
    [build]     const A* const a = b.p.get();
    

    The warning messages reference the core guidelines, in particular I.11, and the support library. However rather than using the support library, it's easier to rewrite directly in terms of unique_ptr, std::make_unique (as has already been mentioned), and encapsulate pointers in classes.

    One possible refactoring is along these lines:

    class B
    {
        std::unique_ptr<A> p;
    
    public:
        void set(std::unique_ptr<A>&& p_) { p = std::move(p_); }
        const auto& get() const { return *p; }
    };
    
    int main()
    {
        B b;
        auto p_ = std::make_unique<A>();
        b.set(std::move(p_));
        std::cout << "Out1: " << b.get().i << std::endl;
    
        const A& a = b.get();
    
        std::cout << "Out2: " << a.i << std::endl;
    }
    

    Note, here a has become a (const) reference, so can't be deleted.

    Footnote: clang-tidy can be set up with CMake using CMAKE_CXX_CLANG_TIDY, with a .clang-tidy file at the root of the project to enable or disable checks (depending on whether you want to have minimal checks, or run all checks and disable the ones that aren't useful).