Menu ▾ ▴

danglingLifetime after transferring unique_ptr ownership

Long Huang
2026-10-28
2026-10-29
  • Long Huang

    Long Huang - 2026-10-28

    A fixture retains a non-owning pointer obtained from a local std::unique_ptr.
    The unique pointer is then moved into a separately implemented owner's
    constructor, which moves it into a member. The pointee outlives the fixture's
    creation method, but cppcheck reports that the observer points to a local
    variable.

    owner.h:

    #pragma once
    #include <memory>
    
    class Callback
    {
    public:
       virtual ~Callback() = default;
       virtual void Invoke() = 0;
    };
    
    class Owner
    {
    public:
       explicit Owner(std::unique_ptr<Callback> callback);
       ~Owner();
       void Invoke();
    
    private:
       std::unique_ptr<Callback> CallbackInstance;
    };
    

    owner.cpp:

    #include "owner.h"
    #include <utility>
    
    Owner::Owner(std::unique_ptr<Callback> callback)
       : CallbackInstance(std::move(callback))
    {
    }
    
    Owner::~Owner() = default;
    
    void Owner::Invoke()
    {
       CallbackInstance->Invoke();
    }
    

    observer.cpp:

    #include "owner.h"
    #include <cassert>
    #include <utility>
    
    class RecordingCallback: public Callback
    {
    public:
       void Invoke() override { ++Count; }
       int Count = 0;
    };
    
    class Fixture
    {
    public:
       void Create()
       {
          std::unique_ptr<RecordingCallback> local = std::make_unique<RecordingCallback>();
          Observer = local.get();
          Subject.reset(new Owner(std::move(local)));
       }
    
       std::unique_ptr<Owner> Subject;
       RecordingCallback* Observer = nullptr;
    };
    
    int main()
    {
       Fixture fixture;
       fixture.Create();
       fixture.Subject->Invoke();
       assert(fixture.Observer->Count == 1);
    }
    

    Version: Cppcheck 2.22.0 (Linux).
    Save the three independent files together in a fresh directory, then run:

    cppcheck --enable=all --library=qt --std=c++17 \
      --suppress=missingIncludeSystem --suppress=missingInclude \
      --suppress=unusedFunction --suppress=unmatchedSuppression \
      --suppress=checkersReport --template=gcc observer.cpp owner.cpp
    

    Expected

    No danglingLifetime diagnostic on Observer = local.get(). Observer refers to
    the dynamically allocated callback, not the local smart-pointer object.
    Owner::CallbackInstance owns that allocation after both moves and remains alive
    when the observer is used. The runtime assertion succeeds.

    Actual

    The exact standalone invocation above emits:

    observer.cpp:18:7: warning: Non-local variable 'Observer' will use pointer to local variable 'local'. [danglingLifetime]
    Observer = local.get();
    ^
    observer.cpp:18:27: note: Raw pointer to smart pointer created here.
    Observer = local.get();
    ^
    observer.cpp:17:42: note: Variable created here.
    std::unique_ptr<RecordingCallback> local = std::make_unique<RecordingCallback>();
    ^
    observer.cpp:18:7: note: Non-local variable 'Observer' will use pointer to local variable 'local'.
    Observer = local.get();
    ^
    
     
  • CHR

    CHR - 2026-10-29

    Thanks for reporting, ticket is here: https://trac.cppcheck.net/ticket/15154

     

Log in to post a comment.