【问题标题】:Qt inheritance and classes constructors confusionQt继承和类构造函数混淆
【发布时间】:2020-01-12 16:36:37
【问题描述】:

我对 Qt C++ 有点陌生,我认为我缺少一个小东西,但不知道是什么。

我正在尝试制作一个简单的 Qt C++ 应用程序只是为了熟悉它,但我遇到了一些问题,首先,我有一个 votor 类,它是主要的应用程序类,还有一个叫做 recorder 的类,它将在主要投票者类中使用。为简单起见,我省略了不相关的部分, 以下是文件:

投票者.h

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_votor.h"

#ifndef TSTRECORD_H
#define TSTRECORD_H
#endif

#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QWidget>
#include "recorder.h"


class VOTor : public QMainWindow
{
    Q_OBJECT

public:
    VOTor(QWidget *parent = Q_NULLPTR);
    recorder xs;

private:
    Ui::VOTorClass ui;

};

投票者.cpp

#include "votor.h"

VOTor::VOTor(QWidget *parent) : QMainWindow(parent), xs(parent)
{
    ui.setupUi(this);
    //xs = recorder();

}

recorder.h

#pragma once
#include <QDebug> 
#include <QObject>
#include <QtCore/qbuffer.h>
#include <QtCore/qiodevice.h>

#include<QtMultimedia/qaudioformat.h>
#include<QtMultimedia/qaudiodeviceinfo.h>
#include<QtMultimedia/qaudioinput.h>

class recorder : public QObject
{
    Q_OBJECT

public:
//  recorder();
    recorder(QObject *parent);
    ~recorder();
        //Some functions related to recording omitted for more focus
private:
      //omitted members for simplicity, just some integers, chars and qt objects not related to problem

recorder.cpp

#include "recorder.h"

recorder::recorder(QObject *parent) : QObject(parent)

{
 //just initializing the omitted members normally
}
//recorder::recorder() {}

recorder::~recorder()
{

}

如您所见,recorder 对象是 votor 类中的成员。现在,我需要调用记录器构造函数来初始化其父级。现在,我知道我不能只做(在 votor.h 中)

recorder xs(parent);

所以, 1-除了初始化列表之外,还有其他方法可以调用记录器构造函数吗? 我想要另一种方式,因为我使用记录器 xs(..) 比使用初始化列表更方便,我觉得(只是觉得)使用初始化列表很重(不是性能方面,而是可读性)。我也知道我可以使用动态分配,但我不想在没有充分理由的情况下使用它。

2- 我决定使用初始化列表来调用记录器构造函数并将 (Qobject* parent) 传递给记录器。代码构建成功,但是在运行时,它给出了访问冲突错误,我不知道为什么...... 它给: "访问冲突读取位置 0x7C32F08D。"

我想我错过了一些小事。我希望知道什么是错的。

编辑: 正如@p-a-o-l-o 所建议的,访问冲突来自省略的代码,所以,我将其发布在这里,因为我不知道我的代码有什么问题:

recoder.h 完整版

class recorder : public QObject
{
    Q_OBJECT

public:
    recorder();
    //recorder(QObject *parent);
    ~recorder();
    void record();
    void stop();

public slots:
    void stateChanged(QAudio::State);

private:
    unsigned char state;
    QBuffer* voiceBuffer;
    QAudioFormat* format;
    QAudioDeviceInfo* info;
    QAudioInput* audioIn;

    unsigned char writeWav(QByteArray*);


};

根据调试模式导致访问冲突的部分是记录器类的构造函数

recorder::recorder() : QObject(Q_NULLPTR)
{

    state = 0;
    voiceBuffer->open(QIODevice::WriteOnly); 
    format->setSampleRate(8000);
    format->setChannelCount(1);
    format->setSampleRate(16);
    format->setByteOrder(QAudioFormat::LittleEndian);
    format->setCodec("audio/pcm");
    format->setSampleType(QAudioFormat::SignedInt);

    *info =  QAudioDeviceInfo::defaultInputDevice();
    audioIn = &QAudioInput(*info, *format);
    //audioIn->stateChanged.connect(stateChanged);
    //connect(audioIn, &QAudioInput::stateChanged, stateChanged);
    connect(audioIn, SIGNAL(stateChanged (QAudio::State) ), this, SLOT(stateChanged(QAudio::State)));
}

【问题讨论】:

  • 如果一个类型没有默认构造函数,那么你必须使用初始化列表。如果类型声明为常量,则相同。这样,编译器可以确保正确构造对象。
  • 对于访问冲突,你应该使用调试器来找出你的代码做错了什么。通常调试器会在错误发生时停止,通过查看代码和变量,它有助于确定问题。无论如何,如果没有重现访问冲突的代码,将很难提供帮助。
  • 程序在组装时停止,我不知道是什么原因。我唯一确定的是问题来自初始化列表中的父级,因为当我更改代码并使用默认构造函数进行记录时,然后使初始化列表不带参数,它运行良好。
  • 当您尝试在堆栈上分配 QObject 时,您可能需要使用初始化列表,就像您在此处所做的那样。但是,在 Qt 中,有必要通过将子对象放在指针中来在堆上分配子对象,除非所有东西都将在程序结束时被销毁。转到 Qt 帮助屏幕,查找 ~QObject 并阅读第一个警告。如果你在堆上分配那么你将有更多的初始化选项
  • @MohammadAKanan 我进一步编辑了答案,但我想你已经猜对了

标签: c++ qt class inheritance class-constructors


【解决方案1】:

关于问题 #1:正如 cmets 中正确建议的那样,QObject 派生类的正确构造函数将具有 nullptr 默认参数:

recorder(QObject *parent = Q_NULLPTR);

如果你真的需要 parent 对象来初始化构造中的其他成员,你别无选择,必须以某种方式调用该构造函数。

否则,使用无参数构造函数并在那里初始化其他成员:

recorder::recorder() : QObject(Q_NULLPTR)
{
    //just initializing the omitted members normally
}

这样的构造函数会被自动调用,这里不需要初始化列表。

如果您仍然需要 recorder 对象的父对象,请在 VOTor 构造函数中给它一个父对象:

VOTor::VOTor(QWidget *parent) : QMainWindow(parent)
{
    ui.setupUi(this);
    xs.setParent(parent);
}

关于问题#2:据我从您发布的代码中可以看出,访问冲突与Qt parenting 无关,必​​须与@ 内的(省略)代码有关987654333@构造函数。

只是为了清除它:为成员对象设置父对象总是安全的,因为它会超出范围之前 ~QObject() 被调用,所以它将从子列表中删除之前 ~QObject() 可能会调用 delete 。

以OP代码为例,析构函数的顺序如下:

~VOTor()
~recorder() ---> xs is removed from the children list
.
.
.
~QObject() ---> will call delete on all children, but ws is not in the list anymore

Qt documentation 非常清楚父母和孩子的构造/销毁顺序:简而言之,如果孩子是在堆栈上创建的,那么一切都会好起来,直到孩子被创建之后父母。 但是,同样,如果子类恰好是父类的成员,由于上述原因,它也可以(即使它的构造实际上发生在其父类之前)。

【讨论】:

  • -1 表示“养育成员对象总是安全的”。我不确定您要通过“成员对象”说什么。你的意思是把一个对象放在它自己的this 上吗?或者你的意思是养育它自己的this.parent?不管怎样,这个说法是错误的。 The Qt docs 给出一个具体的例子,在堆栈上设置一个对象会导致崩溃。
  • @p-a-o-l-o 我编辑了我的问题,你对省略部分的违规行为是正确的。我定义的所有班级成员都会导致违规。我不知道为什么,所以我编辑了我的答案,添加了省略的部分。
  • @MohammadAKanan 有趣的是,您选择了那个特定的帖子。来自同一讨论的更好的帖子是this 以及随后的所有内容,这通过第二个示例证明了此答案中的陈述,即“为成员对象做父母总是安全的”只是错误的。
  • @psimpson 我编辑了答案以进一步澄清。正如您在评论中发布的链接中所述,如果遵循正确的构造顺序,则可以在堆栈上分配子代。我了解您想保护其他人免受您认为错误的信息的影响,但事实并非如此(而且,人们通常足够聪明和谦虚地测试 在否决答案之前的事情,甚至尝试编辑它们)。
猜你喜欢
  • 2017-03-18
  • 2021-11-06
  • 2012-09-03
  • 1970-01-01
  • 2017-09-19
  • 2015-05-13
  • 2014-11-22
相关资源
最近更新 更多