Вопрос или проблема
Я пытался собрать VTK 9.3.1 как статическую библиотеку с помощью Visual Studio 17 2022, но это не удается на этапе линковки:
vtkCommonCore-9.3.lib(vtkSMPToolsAPI.obj) : error LNK2019: unresolved external symbol "public: bool __cdecl vtk::detail::smp::vtkSMPToolsImpl<1>::IsParallelScope(void)" (?IsParallelScope@?$vtkSMPToolsImpl@$00@smp@detail@vtk@@QEAA_NXZ) referenced in function "public: bool __cdecl vtk::detail::smp::vtkSMPToolsAPI::IsParallelScope(void)" (?IsParallelScope@vtkSMPToolsAPI@smp@detail@vtk@@QEAA_NXZ)
(Примечание: как динамическая библиотека, она собирается нормально.)
Я обнаружил, что эта проблема уже была сообщена, например, здесь, но она остается нерешенной.
Я упростил проблему до следующего MRE (перенесено в конец поста для лучшей читаемости).
При установленном стандарте C++17 MinGW компилирует код без проблем, в то время как MSVC выдает ту же ошибку линковки:
main.obj : error LNK2019: unresolved external symbol "public: unsigned __int64 __cdecl Brick<0>::GetNumberOfSides(void)" (?GetNumberOfSides@?$Brick@$0A@@@QEAA_KXZ) referenced in function main
Мне кажется, что по какой-то причине компилятор не может записать определение Brick< ShapeType::Circle >::GetNumberOfSides
в объектный файл Circle.obj
, поэтому линковщик не может его найти.
У меня вопрос: Является ли приведенный выше минимальный пример корректным согласно стандарту C++17?
Другими словами, является ли это ошибкой в компиляторе Microsoft или MinGW (GCC) слишком снисходителен?
Примечание: MSVC компилирует код правильно, когда установлен стандарт C++20.
Brick.h
:
#ifndef Brick_h
#define Brick_h
#include <cstddef>
enum class ShapeType
{
Circle = 0,
Triangle = 3,
Square = 4
};
template< ShapeType Shape >
class Brick
{
public:
std::size_t GetNumberOfSides()
{
return static_cast< std::size_t >( Shape );
}
};
#endif // Brick_h
Circle.cxx
:
#include "Brick.h"
template<>
std::size_t Brick< ShapeType::Circle >::GetNumberOfSides()
{
throw 0;
}
main.cxx
:
#include <iostream>
#include "Brick.h"
template<>
std::size_t Brick< ShapeType::Circle >::GetNumberOfSides();
int main()
{
Brick< ShapeType::Circle > B;
try
{
std::cout << B.GetNumberOfSides();
}
catch( ... )
{
std::cerr << "Необработанное исключение - завершение. ";
}
return 0;
}
CMakeLists.txt
:
cmake_minimum_required( VERSION 3.12...3.29 FATAL_ERROR )
project( Toy VERSION 1.0 )
set( CMAKE_CXX_STANDARD 17 )
set( CMAKE_CXX_STANDARD_REQUIRED True )
add_executable( Toy main.cxx Circle.cxx )
target_include_directories( Toy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} )
Команды сборки:
mkdir build
cd build
cmake ..
cmake --build .
Ответ или решение
Ваш вопрос о проблемах компиляции с использованием MSVC (Microsoft Visual C++) и специализированных классов шаблонов, определённых в отдельном переводном единице, требует внимания к нескольким ключевым аспектам стандартов C++ и особенностей компиляторов.
Проблема
Вы столкнулись с ошибкой компоновки, связанной с неопределённым внешним символом для метода GetNumberOfSides
в классе шаблона Brick
. Это происходит, когда вы используете шаблоны и не обеспечиваете корректную спецификацию для методов шаблонных классов в реальных единицах переводов.
Код и Содержимое Файлов
-
Brick.h
#ifndef Brick_h #define Brick_h #include <cstddef> enum class ShapeType { Circle = 0, Triangle = 3, Square = 4 }; template< ShapeType Shape > class Brick { public: std::size_t GetNumberOfSides() { return static_cast<std::size_t>(Shape); } }; #endif // Brick_h
-
Circle.cxx
#include "Brick.h" template<> std::size_t Brick< ShapeType::Circle >::GetNumberOfSides() { throw 0; }
-
main.cxx
#include <iostream> #include "Brick.h" template<> std::size_t Brick< ShapeType::Circle >::GetNumberOfSides(); int main() { Brick< ShapeType::Circle > B; try { std::cout << B.GetNumberOfSides(); } catch( ... ) { std::cerr << "Uncaught exception - terminating. "; } return 0; }
Проблема Специализации
Ошибка связывается с тем, что MSVC не может найти определение GetNumberOfSides
для классов шаблона Brick<ShapeType::Circle>
. Главная причина этих ошибок заключается в том, что специализированные методы шаблонов должны быть определены в том же переводном единице, где они используются, так как компилятору нужно иметь доступ к их реализации на этапе линковки.
Почему это Работает в C++20?
При переходе на C++20 произошли изменения в правилах спецификации и определения методов шаблонов, что могло позволить MSVC правильно обрабатывать такие случаи и тем самым убирает проблему во время линковки. Однако, по стандартам C++17, такая спецификация приводит к проблемам, как вы заметили.
Как Исправить
Чтобы исправить вашу проблему, необходимо обеспечить, чтобы определение функционала шаблона было доступно компилятору на этапе линковки. Это можно сделать двумя способами:
-
Перенести Определение Метода в Заголовочный Файл
Включите определение методаGetNumberOfSides
в файлBrick.h
непосредственно, что позволит компилятору всегда видеть его.std::size_t GetNumberOfSides() { return static_cast<std::size_t>(Shape); }
-
Использовать
extern
в Заголовочном Файле
Убедитесь, что вCircle.cxx
метод определяется именно в том месте, где он должен быть доступен:template<> std::size_t Brick< ShapeType::Circle >::GetNumberOfSides();
Заключение
Исходя из вышесказанного, ваша проблема с линковкой сообщений в MSVC – это следствие ограничений C++17 в контексте спецификации шаблонов. Это не является багом компилятора Microsoft, а результатом различий в стандартах C++ и реализации шаблонов в различных компиляторах. Чтобы избежать подобных проблем, старайтесь держать определения шаблонов и их специализаций в тех же единицах переводов, где они применяются.