27 三月 2025
从一开始,任务就涉及移植包含多达数百万行代码的多个项目。本质上,对转换器的技术规范可以归结为一句话:“确保所有这些代码都能在 C++ 中正确移植和运行”。负责发布 C++ 产品的人员的工作包括翻译代码、运行测试、准备发布包等等。遇到的问题通常分为以下几类:
处理这个列表是自上而下的 —— 例如,在解决转换后代码的编译问题之前,无法验证其功能和性能。因此,许多长期存在的问题仅在 CodePorting.Translator Cs2Cpp 项目工作的后期阶段才被发现。
最初,在修复由对象间循环依赖引起的简单内存泄漏时,我们对字段应用了 CppWeakPtr
特性,使得字段类型变为 WeakPtr
。只要 WeakPtr
可以通过调用 lock()
方法或隐式地(这在语法上更方便)转换为 SharedPtr
,这并不会引起问题。然而,后来我们还需要使用 CppWeakPtr
特性的特殊语法,使容器内包含的引用变为弱引用,这导致了一些令人不快的意外。
我们采用的方法遇到的第一个麻烦迹象是,从 C++ 的角度来看,MyContainer<SharedPtr<MyClass>>
和 MyContainer<WeakPtr<MyClass>>
是两种不同的类型。因此,它们不能存储在同一个变量中,不能传递给同一个方法,不能从方法返回等等。这个原本仅用于管理对象字段中引用存储方式的特性,开始出现在越来越奇怪的上下文中,影响了返回值、参数、局部变量等。负责处理它的转换器代码变得日益复杂。
第二个问题也是我们未曾预料到的。对于 C# 程序员来说,每个对象拥有一个关联集合是很自然的事情,该集合既包含当前对象拥有且无法通过其他方式访问的对象的唯一引用,也包含对父对象的引用。这样做是为了优化从某些文件格式的读取操作,但对我们来说,这意味着同一个集合可能同时包含强引用和弱引用。指针类型不再是其操作模式的最终决定因素。
显然,这两个问题在现有范式内无法解决,指针类型需要重新考虑。这种重新审视的结果是 SmartPtr
类,它提供了一个 set_Mode()
方法,接受两个值之一:SmartPtrMode::Shared
和 SmartPtrMode::Weak
。所有 SmartPtr
构造函数都接受相同的值。因此,每个指针实例可以存在于以下两种状态之一:
模式之间的切换可以在运行时随时发生。只有当至少存在一个对该对象的弱引用时,才会创建弱引用计数器。
我们的指针支持的完整功能列表如下:
intrusive_ptr
语义:为同一对象创建的任意数量的指针将共享一个引用计数器。->
):用于访问所指向的对象。SmartPtr
类是模板化的,不包含虚方法。它与负责存储引用计数器的 System::Object
类紧密耦合,并且只与其派生类一起工作。
存在一些与典型指针行为的偏差:
为了保持与旧代码的兼容性,SharedPtr
类型成为了 SmartPtr
的别名。WeakPtr
类现在继承自 SmartPtr
,不添加任何字段,仅重写构造函数以始终创建弱引用。
容器现在总是以 MyContainer<SmartPtr<MyClass>>
语义进行移植,存储引用的类型在运行时选择。对于基于 STL 数据结构手动编写的容器(主要是 System
命名空间中的容器),默认引用类型使用自定义分配器设置,但仍允许更改单个容器元素的模式。对于转换的容器,切换引用存储模式所需的代码由转换器生成。
此解决方案的缺点主要包括指针创建、复制和删除操作期间性能下降,因为在通常的引用计数之外增加了对引用类型的强制检查。具体数字在很大程度上取决于测试结构。目前正在讨论在指针类型保证不变的地方生成更优化的代码。
我们的移植方法要求在源 C# 代码中手动放置特性来标记哪些引用应该是弱引用。未正确放置这些特性的代码在转换后会导致内存泄漏,在某些情况下还会导致其他错误。带有特性的代码大致如下:
struct S {
MyClass s; // 对对象的强引用
[CppWeakPtr]
MyClass w; // 对对象的弱引用
MyContainer<MyClass> s_s; // 对强引用容器的强引用
[CppWeakPtr]
MyContainer<MyClass> w_s; // 对强引用容器的弱引用
[CppWeakPtr(0)]
MyContainer<MyClass> s_w; // 对弱引用容器的强引用
[CppWeakPtr(1)]
Dictionary<MyClass, MyClass> s_s_w; // 对容器的强引用,其中键通过强引用存储,值通过弱引用存储
[CppWeakPtr, CppWeakPtr(0)]
Dictionary<MyClass, MyClass> w_w_s; // 对容器的弱引用,其中键通过弱引用存储,值通过强引用存储
}
在某些情况下,需要手动调用 SmartPtr
类的别名构造函数或其设置存储引用类型的方法。我们尽量避免编辑移植后的代码,因为每次运行转换器后都必须重新应用此类更改。相反,我们力求将此类代码保留在 C# 源代码中。我们有两种方法可以实现这一点:
class Service {
// 什么也不做
public static void SetWeak<T>(T arg) {}
}
class Service {
public:
template <typename T> static void SetWeak(SmartPtr<T> &arg)
{
// 设置为弱引用模式
arg.set_Mode(SmartPtrMode::Weak);
}
};
class MyClass {
private Dictionary<string, object> data;
public void Add(string key, object value)
{
data.Add(key, value);
//CPPCODE: if (key == u"Parent") data->data()[key].set_Mode(SmartPtrMode::Weak);
}
}
这里,System::Collections::Generic::Dictionary
中的 data()
方法返回对该容器底层 std::unordered_map
的引用。