Ошибка компоновщика при реализации идиомы pimpl

#c #linker #pimpl-idiom

#c #компоновщик #pimpl-идиома

Вопрос:

Отредактировано для обеспечения большей ясности. Извиняюсь за то, что всех запутал.

Это под Windows.

У меня есть статическая библиотека, которая реализует класс с использованием идиомы pimpl. Заголовок pimpl не только используется использующим кодом, но и ссылается на статическую библиотеку. Тем не менее, когда я компилирую потребляющий код (.exe), компоновщик жалуется на неразрешенные внешние элементы в классе реализации, которые предположительно скрывает заголовок pimpl.

Как это может быть возможно?

 // Foo.lib

// Foo.h

class FooImpl;

class Foo
{
    std::shared_ptr<FooImpl> pimpl_;    
public:
    Foo();
};

// Foo.cpp

#include "Foo.h"
#include "FooImpl.h"

Foo::Foo() : pimpl_(new FooImpl())
{
}

// This is how its used in Bar.exe
// Bar.exe links against Foo.lib

// Bar.h

#include "Foo.h"

class Bar
{
    Foo access_foo_;
};

// Bar.cpp

#include "Bar.h"
  

Когда я компилирую / связываю Bar.exe , компоновщик жалуется, что он не может разрешить FooImpl. Я забыл, что именно он сказал, поскольку сейчас у меня нет доступа к моему рабочему компьютеру, но в этом суть. Ошибка не имела для меня смысла, потому что цель перехода по маршруту pimpl заключалась в том, чтобы Bar.exe не нужно было беспокоиться о FooImpl.

Точная ошибка выглядит следующим образом:

1> Foo.lib(Foo.obj) : ошибка LNK2019: неразрешенный внешний символ «public: __thiscall FooImpl::FooImpl(void)» (??0FooImpl@@QAE @XZ), на который ссылается функция «public: __thiscall Foo::Foo(void)» (??0Foo@@QAE@XZ)

Комментарии:

1. У вас ошибка в строке 3; в ней должно быть написано «foo», а не «bar».

2. Вы не даете нам особых шансов — как насчет того, чтобы рассказать нам, что вы пробовали, или показать нам некоторые выходные данные компилятора, или показать нам, что вы включили в начало своих файлов, или, может быть, показать makefile, если вы его использовали? Им мы, вероятно, могли бы помочь 🙂

Ответ №1:

Когда вы создаете статическую библиотеку, компоновщик не пытается устранить все, чего не хватает; предполагается, что вы позже свяжете его с другой библиотекой или что сам исполняемый файл внесет некоторую недостающую функцию. Вы, должно быть, забыли включить какой-то важный файл реализации в проект библиотеки.

Другая возможность заключается в том, что класс реализации pimpl является шаблонным классом. Шаблоны не генерируют код немедленно, компилятор ждет, пока вы не попытаетесь использовать их с заполненными параметрами шаблона. Ваш файл реализации должен содержать экземпляр шаблона с параметрами, которые будут поддерживаться вашей библиотекой.


После просмотра вашего редактирования проблема заключается в том, что конструктору Foo::Foo требуется доступ к конструктору FooImpl::FooImpl, но компоновщик не знает, где его найти. Когда компоновщик собирает библиотеку, он не пытается разрешить все ссылки в это время, потому что у него могут быть зависимости от еще одной библиотеки. Единственный раз, когда все должно быть решено, — это когда он собирает исполняемый файл. Таким образом, хотя Bar не нужно знать о FooImpl напрямую, он все равно зависит от него.

Вы можете решить эту проблему одним из двух способов: либо экспортировать FooImpl из библиотеки вместе с Foo, либо убедиться, что Foo имеет доступ к FooImpl во время компиляции, поместив их оба в Foo.cpp с FooImpl, идущим перед Foo.

Комментарии:

1. Я отредактировал свой вопрос для большей ясности. Черт возьми, мой класс pimpl не является шаблоном.

2. @Dilip, ты становишься жертвой моего первого утверждения — у вашего Foo::Foo нет доступа к FooImpl::FooImpl . Либо убедитесь, что FooImpl::FooImpl экспортирован из библиотеки, либо поместите его в Foo.cpp впереди Foo::Foo.

3. большое спасибо! Думаю, теперь я понимаю. Я попробую это завтра на работе.

4. Включение FooImpl в Foo.cpp исправлены ошибки компоновщика. Я не хотел экспортировать, потому что это почему-то казалось нелогичным делать это из статической библиотеки

Ответ №2:

1> Foo.lib(Foo.obj) : ошибка LNK2019: неразрешенный внешний символ «public: __thiscall FooImpl::FooImpl(void)» (??0FooImpl@@QAE @XZ), на который ссылается функция «public: __thiscall Foo::Foo(void)» (??0Foo@@QAE@XZ)

Компоновщик жалуется вам, что он не может найти определение FooImpl::FooImpl() . Здесь вы вызываете конструктор —

 Foo::Foo() : pimpl_(new FooImpl())
                    // ^^^^^^^^^ Invoking the default constructor.
  

Вы только что предоставили объявление конструктора по умолчанию, но не его определение.

Комментарии:

1. Я не понимаю. Эта ошибка не возникает при сборке Foo.lib. Это происходит, когда я создаю Bar.exe (это связано с Foo.lib). Это то, что меня смущает. Основная причина существования класса Foo pimpl заключается в том, что Bar.exe не обязательно иметь зависимость от FooImpl, верно?

2. Возможно, я ошибочно использую идиому брандмауэра компилятора для достижения разделения интерфейса и реализации. Я предоставляю Bar.exe только с Foo.h и разрешить Bar.exe чтобы связать с Foo. библиотека надеется, что это устранит необходимость Bar.exe беспокоиться о FooImpl. Кажется, я там ошибся?

3. "I don't understand. This error is not happening when I build Foo.lib." Вероятно, в созданных вами файлах вы не создавали экземпляр, FooImpl который вызывает конструктор по умолчанию.