在 Objective-C++ (.mm) 文件中使用模板化 class 时的未定义符号

Undefined symbol when using a templated class in Objective-C++ (.mm) file

这是使用 Xcode 版本 13.2.1 创建的 Objective-C macOS 项目。在项目内部,我有一个名为“plistModifier”的模板化 class。 class 用于为任何 plist 设置自定义类型的值(例如 NSStringNSNumber 等)。 class 的头文件名为“plistModifier.h”,它看起来像这样:

#ifndef plistModifier_h
#define plistModifier_h

#include <iostream>
#include <string>
#import <Foundation/Foundation.h>
#include <unistd.h>

template <class type>
class plistModifier {
       
public:
    void modifyPref(std::string key, type value);
    type getPref(std::string key);
};


#endif /* plistModifier_h */

class 的实现在单独的“plistModifier.mm”文件中如下:

#include "plistModifier.h"

template <class type>
void plistModifier<type>::modifyPref(std::string key, type val) {
    
    NSString* preferencePlist = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/com.rA9.LeetDownPreferences.plist"];
    NSDictionary* dict=[[NSDictionary alloc] initWithContentsOfFile:preferencePlist];
    [dict setValue:val forKey:[NSString stringWithUTF8String:val]];
    [dict writeToFile:preferencePlist atomically:YES];
    
}
template <class type>
type plistModifier<type>::getPref(std::string key) {
    
    NSString *preferencePlist = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/com.rA9.LeetDownPreferences.plist"];
    NSDictionary *dict=[[NSDictionary alloc] initWithContentsOfFile:preferencePlist];
    return dict[key];
}

问题是,当我创建一个 plistModifier 对象并调用它的方法时,编译器抛出错误 Undefined symbols for architecture x86_64: "plistModifier<int>::modifyPref(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, int)", referenced from: -[SettingsVC debuggingToggle:] in SettingsVC.o

这就是我在 SettingsVC.mm 文件中调用对象方法的方式:

#import "SettingsVC.h"
#include "plistModifier.h"

plistModifier<int> plistObject;

- (IBAction)debuggingToggle:(id)sender {
    
    plistObject.modifyPref("DebugEnabled", _debugToggle.state);
}

我曾尝试在一个空的 C++ 项目中使用相同的模板化 class,它没有给我任何错误。您认为问题是什么?

无法拆分模板声明 (.h) 和定义 (.mm/.cpp)。 您必须在 .h 文件中保留模板方法实现。

Refer to this explanation

https://isocpp.org/wiki/faq/templates#templates-defn-vs-decl

但是由于您的实现没有使用通用类型 type 属性,您可以通过在 .mm 中根据 id type 实现它来拆分它,然后在 .h 中添加模板包装器文件:

// .mm

void plistModifierImpl::modifyPref(std::string key, id val) {    
    NSString *preferencePlist = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/com.rA9.LeetDownPreferences.plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:preferencePlist];
    [dict setValue:val forKey:[NSString stringWithUTF8String:val]];
    [dict writeToFile:preferencePlist atomically:YES];    
}
// .h

class plistModifierImpl; // define this in .mm

template <class type>
class plistModifier {
    // private loosely-typed implementation
    std::unique_ptr<plistModifierImpl> p_impl;

public:
    // public strongly-typed wrapper
    void modifyPref(std::string key, type value) {
        p_impl->modifyPref(key, value);
    }
    ...
}

只要 type 可以隐式转换为 id,这对 NSString、NSNumber、NSDate、NSArray 和 NSDictionary 都是正确的。

如果您需要支持像 int 这样的原始类型,您需要使用调整后的实现来专门化模板。