Qt / Qml和方法重载

时间:2015-02-15 18:48:48

标签: c++ qt qml moc

在从Qml中调用重载的C ++方法并尝试理解其背后的原因时,遇到了Qt框架的奇怪行为。让我们说我有一个类似QList<QVariant>的类,其中包含以下方法:

...
Q_SLOT void append(const QVariant &item);
Q_SLOT void append(const QVariantList &items);
Q_SLOT void insert(int index, const QVariant &item);
Q_SLOT void insert(int index, const QVariantList &items);
...

设为Qml:

onclicked: {
  var itemCount = myListObject.size();
  myListObject.insert(itemCount, "Item " + (itemCount + 1));
}

Qt以某种方式决定调用void insert(int index, const QVariantList &items)重载,items参数设置为一个null QVariant 一个空QVariantList而不是{{1}包含在void insert(int index, const QVariant &item)中的QString重载。

现在,如果按照以下方式更改声明的顺序,它将按预期工作:

QVariant

我在Qt框架的文档中找不到任何关于声明顺序以及它如何影响调用方法解析方式的文档。

有人可以解释一下吗?谢谢。

2 个答案:

答案 0 :(得分:4)

此问题与JavaScript中的重载有关。一旦你了解它 - 你就会理解你的代码“奇怪的行为”的原因。只需看看Function overloading in Javascript - Best practices

简而言之 - 我建议你做下一步:因为你可以在两边操作QVariant变量(QML和Qt / C ++) - 将变量作为参数传递,并在Qt / C ++端处理它如你所愿。

您可以使用以下内容:

您的C ++类已创建并传递给QML(例如setContextProperty("TestObject", &tc)):

public slots:
    Q_SLOT void insert(const int index, const QVariant &something) {
        qDebug() << __FUNCTION__;
        if (something.canConvert<QString>()) {
            insertOne(index, something.toString());
        } else if (something.canConvert<QStringList>()) {
            insertMany(index, something.toStringList());
        }
    }

private slots:
    void insertOne(int index, const QString &item) {
        qDebug() << __FUNCTION__ << index << item;
    }

    void insertMany(int index, const QStringList &items) {
        qDebug() << __FUNCTION__ << index << items;
    }

QML 中的某个地方:

Button {
    anchors.centerIn: parent
    text: "click me"
    onClicked: {
        // Next line will call insertOne
        TestObject.insert(1, "Only one string")
        // Next line will call insertMany
        TestObject.insert(2, ["Lots", "of", "strings"])
        // Next line will call insertOne
        TestObject.insert(3, "Item " + (3 + 1))
        // Next line will call insertOne with empty string
        TestObject.insert(4, "")
        // Next line will will warn about error on QML side:
        // Error: Insufficient arguments
        TestObject.insert(5)
    }
}

答案 1 :(得分:1)

<块引用>

这个问题与 JavaScript 中的重载有关。一旦你到达 知道它 - 你理解你的“奇怪行为”的原因 代码。

据我所知,JS 没有在 ECMA 规范中定义的重载支持。各个地方都建议使用 JS hacks 来模拟重载支持。

关于 Qt 中行为的正确答案如下:

依赖顺序的行为仅记录在源代码中,作为注释。参见 qtdeclarative 模块中的 qv4qobjectwrapper.cpp。

Resolve the overloaded method to call.  The algorithm works conceptually like this:
    1.  Resolve the set of overloads it is *possible* to call.
        Impossible overloads include those that have too many parameters or have parameters
        of unknown type.
    2.  Filter the set of overloads to only contain those with the closest number of
        parameters.
        For example, if we are called with 3 parameters and there are 2 overloads that
        take 2 parameters and one that takes 3, eliminate the 2 parameter overloads.
    3.  Find the best remaining overload based on its match score.
        If two or more overloads have the same match score, call the last one.  The match
        score is constructed by adding the matchScore() result for each of the parameters.

实现:

static QV4::ReturnedValue CallOverloaded(const QQmlObjectOrGadget &object, const QQmlPropertyData &data,
                                         QV4::ExecutionEngine *engine, QV4::CallData *callArgs, const QQmlPropertyCache *propertyCache,
                                         QMetaObject::Call callType = QMetaObject::InvokeMetaMethod)
{
    int argumentCount = callArgs->argc();

    QQmlPropertyData best;
    int bestParameterScore = INT_MAX;
    int bestMatchScore = INT_MAX;

    QQmlPropertyData dummy;
    const QQmlPropertyData *attempt = &data;

    QV4::Scope scope(engine);
    QV4::ScopedValue v(scope);

    do {
        QQmlMetaObject::ArgTypeStorage storage;
        int methodArgumentCount = 0;
        int *methodArgTypes = nullptr;
        if (attempt->hasArguments()) {
            int *args = object.methodParameterTypes(attempt->coreIndex(), &storage, nullptr);
            if (!args) // Must be an unknown argument
                continue;

            methodArgumentCount = args[0];
            methodArgTypes = args + 1;
        }

        if (methodArgumentCount > argumentCount)
            continue; // We don't have sufficient arguments to call this method

        int methodParameterScore = argumentCount - methodArgumentCount;
        if (methodParameterScore > bestParameterScore)
            continue; // We already have a better option

        int methodMatchScore = 0;
        for (int ii = 0; ii < methodArgumentCount; ++ii) {
            methodMatchScore += MatchScore((v = QV4::Value::fromStaticValue(callArgs->args[ii])),
                                           methodArgTypes[ii]);
        }

        if (bestParameterScore > methodParameterScore || bestMatchScore > methodMatchScore) {
            best = *attempt;
            bestParameterScore = methodParameterScore;
            bestMatchScore = methodMatchScore;
        }

        if (bestParameterScore == 0 && bestMatchScore == 0)
            break; // We can't get better than that

    } while ((attempt = RelatedMethod(object, attempt, dummy, propertyCache)) != nullptr);

    if (best.isValid()) {
        return CallPrecise(object, best, engine, callArgs, callType);
    } else {
        QString error = QLatin1String("Unable to determine callable overload.  Candidates are:");
        const QQmlPropertyData *candidate = &data;
        while (candidate) {
            error += QLatin1String("\n    ") +
                     QString::fromUtf8(object.metaObject()->method(candidate->coreIndex())
                                       .methodSignature());
            candidate = RelatedMethod(object, candidate, dummy, propertyCache);
        }

        return engine->throwError(error);
    }
}

我发现 Qt 4.8 文档 (https://doc.qt.io/archives/qt-4.8/qtbinding.html) 中提到了重载支持。它没有涉及任何细节:

<块引用>

QML 支持调用重载的 C++ 函数。如果有 多个具有相同名称但不同参数的 C++ 函数, 将根据数量和类型调用正确的函数 提供的参数。

请注意,Qt 4.x 系列确实很旧并且已存档。我看到 Qt 5.x 源代码中也存在相同的注释:

src/qml/doc/src/cppintegration/exposecppattributes.qdoc:QML supports the calling of overloaded C++ functions. If there are multiple C++

我同意“按设计”拥有这种依赖顺序的逻辑真的很奇怪。为什么这是理想的行为?如果您非常仔细地阅读该函数的源代码,就会发现其中有更多惊喜(至少最初是这样)。当您在调用站点提供太少的参数时,您会收到一条错误消息列出可用的重载,当您提供太多的参数时,额外的参数将被默默忽略。示例:

Q_INVOKABLE void fooBar(int) {
    qDebug() << "fooBar(int)";
};
Q_INVOKABLE void fooBar(int, int) {
    qDebug() << "fooBar(int, int)";
};

Qml 调用站点。

aPieChart.fooBar();

错误信息。

./chapter2-methods 
qrc:/app.qml:72: Error: Unable to determine callable overload.  Candidates are:
    fooBar(int,int)
    fooBar(int)

Qml 调用站点。

aPieChart.fooBar(1,1,1);

运行时输出(最后一个 arg 被忽略,因为选择了两个 arg 重载):

fooBar(int, int)

根据 What happens if I call a JS method with more parameters than it is defined to accept? 传递过多的参数是一个有效的语法。

还值得注意的是,重载机制支持默认参数值。详情如下:

Qt Quick 中的实现依赖于 Qt Meta Object 系统进行枚举 函数重载。元对象编译器为每个对象创建一个重载 带有默认值的参数。例如:

void doSomething(int a = 1, int b = 2);
             

成为(CallOverloaded() 将考虑所有这三个):

void doSomething();
void doSomething(a);
void doSomething(a, b);