不透明容器#
通常,当调用接受相应 C++ 容器的 C++ 函数时,会传入 Python 容器,如 list
或 dict
(参见 容器类型)。
这意味着在每次调用中,整个 Python 容器都将转换为 C++ 容器,这在例如从点列表创建绘图时可能效率低下。
为了解决这个问题,可以生成特殊的不透明容器,这些容器直接包装底层 C++ 容器(目前为实现 list
类型)。它们实现了序列协议,可以传递给函数而不是 Python 列表。可以像使用 C++ 容器函数一样直接将添加或删除元素等操作应用于它们。
这是通过在 容器类型 的 opaque-containers
属性中指定名称和实例化类型,或使用现有容器类型的 不透明容器 元素来实现的。
第二个用例是容器类型的公共字段。在正常情况下,它们在读取访问时转换为 Python 容器。通过字段修改(参见 修改字段),可以获取不透明容器,该容器避免转换并允许直接修改元素。
可以修改返回引用的获取器以返回不透明容器。这是通过修改返回类型为不透明容器的名称来完成的(参见 替换类型)。
下表列出了除了序列协议(通过索引和 len()
进行元素访问)之外的支持不透明序列容器的函数。STL 和 Qt 命名约定(类似于 Python 的)都受到支持
函数 |
描述 |
|
将 value 添加到序列中。 |
|
将 value 添加到序列开头。 |
|
清除序列。 |
|
删除最后一个元素。 |
|
移除第一个元素。 |
|
对于支持该功能的容器(如 |
|
对于支持该功能的容器(如 |
|
对于支持该功能的容器(如 |
|
对于支持该功能的容器(如 |
注意
std::span
由于是一个非拥有容器,目前在使用作参数传递时被替换为 std::vector
。这意味着从函数中获得的封装 std::span
的不可见容器将在传递给接受 std::span
的函数时通过序列转换转换为 std::vector
。封装 std::vector
的不可见容器可以不加转换地传递。目前这是实验性的,可能会改变。
以下是一个创建一个名为 IntVector
的不可见容器,该容器由 std::vector<int> 的示例,并在 Python 中使用的例子。
我们将考虑三种不同的用法。
情况 1 - 当将 Python 列表作为不可见容器传递给 C++ 函数 TestOpaqueContainer.getVectorSum(const std::vector<int>&)
时
class TestOpaqueContainer
{
public:
static int getVectorSum(const std::vector<int>& intVector)
{
return std::accumulate(intVector.begin(), intVector.end(), 0);
}
};
情况 2 - 我们有一个名为 TestOpaqueContainer
的 C++ 类,其中有 std::vector<int>
公共变量
class TestOpaqueContainer
{
public:
std::vector<int> intVector;
};
情况 3 - 我们有一个名为 TestOpaqueContainer
的 C++ 类,其中有 std::vector<int>
私有变量,且该变量通过 getter 方法通过引用返回。
class TestOpaqueContainer
{
public:
std::vector<int>& getIntVector()
{
return this->intVector;
}
private:
std::vector<int> intVector;
};
注意
情况 2 和 3 通常被认为是在 C++ 中的糟糕类设计。然而,这些例子中的目的更多的是展示 Shiboken 中不可见容器带来的不同可能性,而不是类设计。
在这三种情况中,我们都想通过一个不可见容器在 Python 中使用 intVector
。首先要做的就是在一个类型系统中创建相应的 <container-type />
属性,让 Shiboken 认识到 IntVector
。
<container-type name="std::vector" type="vector" opaque-containers="int:IntVector">
<include file-name="vector" location="global"/>
<conversion-rule>
<native-to-target>
<insert-template name="shiboken_conversion_cppsequence_to_pylist"/>
</native-to-target>
<target-to-native>
<add-conversion type="PySequence">
<insert-template name="shiboken_conversion_pyiterable_to_cppsequentialcontainer"/>
</add-conversion>
</target-to-native>
</conversion-rule>
</container-type>
对于其余的步骤,我们将分别考虑这三种情况。
情况 1 - 当将 Python 列表传递给 C++ 函数时
接下来,我们为 TestOpaqueContainer
类创建一个类型系统条目。
<value-type name="TestOpaqueContainer" />
在这种情况下,类型系统条目简单,函数 getVectorSum(const std::vector<int>&)
接受 IntVector
作为参数。这是因为内在地 IntVector
与 std::vector<int>
是相同的。
现在,构建代码以创建 *_wrapper.cpp
和 *.so
文件,我们将这些文件导入 Python 中。
验证 Python 中的使用
>>> vector = IntVector()
>>> vector.push_back(2)
>>> vector.push_back(3)
>>> len(vector)
2
>>> TestOpaqueContainer.getVectorSum(vector)
vector sum is 5
案例 2 - 当变量是公共的
我们为类 TestOpaqueContainer
创建一个类型系统条目。
<value-type name="TestOpaqueContainer">
<modify-field name="intVector" opaque-container="yes"/>
</value-type>
在 <modify-field />
中注意 opaque-container="yes"
。由于 intVector
的类型是 std::vector<int>
,它选取了 IntVector
不透明容器。
构建代码以创建 *_wrapper.cpp
和 *.so
文件,我们将这些文件导入 Python 中。
验证 Python 中的使用
>>> test = TestOpaqueContainer()
>>> test
<Universe.TestOpaqueContainer object at 0x7fe17ef30c30>
>>> test.intVector.push_back(1)
>>> test.intVector.append(2)
>>> len(test.intVector)
2
>>> test.intVector[1]
2
>>> test.intVector.removeLast()
>>> len(test.intVector)
1
案例 3 - 当变量是私有的并通过对 getter 的引用返回时
与前面的案例类似,我们为类 TestOpaqueContainer
创建一个类型系统条目。
<value-type name="TestOpaqueContainer">
<modify-function signature="getIntVector()">
<modify-argument index="return">
<replace-type modified-type="IntVector" />
</modify-argument>
</modify-function>
</value-type>
在这种情况下,我们在 <replace-type />
字段中指定了不透明容器的名称 IntVector
。
构建代码以创建 *_wrapper.cpp 和 *.so 文件,我们将这些文件导入 Python 中。
验证 Python 中的使用
>>> test = TestOpaqueContainer()
>>> test
<Universe.TestOpaqueContainer object at 0x7f62b9094c30>
>>> vector = test.getIntVector()
>>> vector
<Universe.IntVector object at 0x7f62b91f7d00>
>>> vector.push_back(1)
>>> vector.push_back(2)
>>> len(vector)
2
>>> vector[1]
2
>>> vector.removeLast()
>>> len(vector)
1
在所有三个案例中,如果我们检查模块对应的包装类,我们将看到以下行
static PyMethodDef IntVector_methods[] = {
{"push_back", reinterpret_cast<PyCFunction>(
ShibokenSequenceContainerPrivate<std::vector<int >>::push_back),METH_O, "push_back"},
{"append", reinterpret_cast<PyCFunction>(
ShibokenSequenceContainerPrivate<std::vector<int >>::push_back),METH_O, "append"},
{"clear", reinterpret_cast<PyCFunction>(
ShibokenSequenceContainerPrivate<std::vector<int >>::clear), METH_NOARGS, "clear"},
{"pop_back", reinterpret_cast<PyCFunction>(
ShibokenSequenceContainerPrivate<std::vector<int >>::pop_back), METH_NOARGS,
"pop_back"},
{"removeLast", reinterpret_cast<PyCFunction>(
ShibokenSequenceContainerPrivate<std::vector<int >>::pop_back), METH_NOARGS,
"removeLast"},
{nullptr, nullptr, 0, nullptr} // Sentinel
};
这意味着,上述方法可用于在 Python 中使用 IntVector
不透明容器。