不透明容器#

通常,当调用接受相应 C++ 容器的 C++ 函数时,会传入 Python 容器,如 listdict(参见 容器类型)。

这意味着在每次调用中,整个 Python 容器都将转换为 C++ 容器,这在例如从点列表创建绘图时可能效率低下。

为了解决这个问题,可以生成特殊的不透明容器,这些容器直接包装底层 C++ 容器(目前为实现 list 类型)。它们实现了序列协议,可以传递给函数而不是 Python 列表。可以像使用 C++ 容器函数一样直接将添加或删除元素等操作应用于它们。

这是通过在 容器类型opaque-containers 属性中指定名称和实例化类型,或使用现有容器类型的 不透明容器 元素来实现的。

第二个用例是容器类型的公共字段。在正常情况下,它们在读取访问时转换为 Python 容器。通过字段修改(参见 修改字段),可以获取不透明容器,该容器避免转换并允许直接修改元素。

可以修改返回引用的获取器以返回不透明容器。这是通过修改返回类型为不透明容器的名称来完成的(参见 替换类型)。

下表列出了除了序列协议(通过索引和 len() 进行元素访问)之外的支持不透明序列容器的函数。STL 和 Qt 命名约定(类似于 Python 的)都受到支持

函数

描述

push_back(value), append(value)

value 添加到序列中。

push_front(value), prepend(value)

value 添加到序列开头。

clear()

清除序列。

pop_back(), removeLast()

删除最后一个元素。

pop_front(), removeFirst()

移除第一个元素。

reserve(size)

对于支持该功能的容器(如 std::vectorQList),至少为 size 个元素分配内存,防止重新分配。

capacity()

对于支持该功能的容器(如 std::vectorQList),返回在不重新分配的情况下可以存储的元素数量。

data()

对于支持该功能的容器(如 std::vectorQList),返回一个缓冲区,用于查看内存。

constData()

对于支持该功能的容器(如 std::vectorQList),返回一个只读缓冲区,用于查看内存。

注意

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 作为参数。这是因为内在地 IntVectorstd::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 不透明容器。

注意

绘图示例 展示了使用不透明容器 QPointList 的示例,该容器包装了 C++ 中的 QList<QPoint>。QPointList 可在此处找到对应的类型系统文件 这里