#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
который вызывает конструктор по умолчанию.