Search code examples
c++visual-c++c++20visual-studio-2022pimpl-idiom

"multiply defined symbols found" how is my destructor being defined twice?


Question: What am I doing to cause a multiple definition symbol linker error?

OSFrameworkWindows10Module.ixx

module;

#include <memory>

export module OSFrameworkWindows10Module;

export class OSFramework {
private:
    struct impl;
    std::unique_ptr<impl> _impl;
public:
    OSFramework();
    ~OSFramework();
};

typedef struct OSFramework::impl {
    impl();
    ~impl();
} impl;

impl::impl()
{}

impl::~impl() = default;

OSFramework::OSFramework() : _impl{ std::make_unique<impl>() } {}
OSFramework::~OSFramework() = default;

winmain.cpp

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

import OSFrameworkWindows10Module;

int WINAPI wWinMain (
    _In_ HINSTANCE hinst,
    _In_opt_ HINSTANCE,
    _In_ PWSTR,
    _In_ int
)
{
    auto os_framework = std::make_unique<OSFramework>();
    return S_OK;
};

compiler output

1>------ Build started: Project: Vegas22Metroidvania1, Configuration: Debug x64 ------
1>Scanning sources for module dependencies...
1>OSFrameworkWindows10Module.ixx
1>Compiling...
1>OSFrameworkWindows10Module.ixx
1>winmain.cpp
1>OSFrameworkWindows10Module.ixx.obj : error LNK2005: "public: __cdecl OSFramework::~OSFramework(void)" (??1OSFramework@@QEAA@XZ::<!OSFrameworkWindows10Module>) already defined in winmain.obj
1>D:\projects\Vegas22Metroidvania1\x64\Debug\Vegas22Metroidvania1.exe : fatal error LNK1169: one or more multiply defined symbols found
1>Done building project "Vegas22Metroidvania1.vcxproj" -- FAILED.

Pretty lost here as to why OSFramework::~OSframework() is being counted as defined more than once. I'm somehow instantiating it in my winmain.cpp file even though I'm just setting up a smart pointer for the class instance.

Maybe relevant info: I'm using Visual Studio 17.4.3


Solution

  • I ended up separating implementation from interface:

    winmain.cpp

    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    
    #include <memory>
    
    import OSFrameworkWindows10Module;
    
    int WINAPI wWinMain (
        _In_ HINSTANCE hinst,
        _In_opt_ HINSTANCE,
        _In_ PWSTR,
        _In_ int
    )
    {
        auto os_framework = std::make_unique<OSFramework>();
        return S_OK;
    };
    
    

    OSFrameworkWindows10Module.ixx

    module;
    
    #include <memory>
    
    export module OSFrameworkWindows10Module;
    
    export class OSFramework {
    private:
        struct impl;
        std::unique_ptr<impl> _impl;
    public:
        OSFramework();
        ~OSFramework();
    };
    
    

    OSFrameworkWindows10.cpp (new)

    #include <memory>
    
    import OSFrameworkWindows10Module;
    
    using impl = struct OSFramework::impl {
        impl();
        ~impl();
    };
    
    impl::impl()
    {}
    
    impl::~impl() = default;
    
    OSFramework::OSFramework() :_impl{ std::make_unique<impl>() } {}
    OSFramework::~OSFramework() = default;
    

    compiler output

    1>------ Build started: Project: Vegas22Metroidvania1, Configuration: Debug x64 ------
    1>Scanning sources for module dependencies...
    1>OSFrameworkWindows10Module.ixx
    1>Compiling...
    1>OSFrameworkWindows10Module.ixx
    1>OSFrameworkWindows10.cpp
    1>winmain.cpp
    1>Generating Code...
    1>Vegas22Metroidvania1.vcxproj -> D:\projects\Vegas22Metroidvania1\x64\Debug\Vegas22Metroidvania1.exe
    

    I want to thank @alanbirtles for the tip about the inline keyword when used in a header file, because this got me thinking about how I would normally do this just using a header file and a source file. This allowed me to re-read some blog posts about separating module interface and implementation and better understand what was being discussed.