【Qt】不透明指针(Opaque Pointer)在Qt源码中的应用

本文介绍了不透明指针的概念,通过Person类的实例展示如何在Qt中使用不透明指针来保护数据隐私,以及在QLabel等Qt组件中的实际应用,重点讲解了与之相关的宏Q_DECLARE_PRIVATE和Q_D的使用。

什么是不透明指针(Opaque Pointer)

GeeksforGeeks中给的定义如下:

An opaque pointer is a pointer that points to a data structure whose contents are not exposed at the time of its definition.
不透明指针是一种指针,这种指针指向的内容是不公开的。

文字描述太抽象,我们通过代码展示什么是不透明指针,为什么要使用它。

假设我们实现一个Person类,保存信息并支持打印,实现很简单:

// person.h
#ifndef _PERSON_H_
#define _PERSON_H_

#include <string>

class Person {
private:
    std::string name;
public:
    Person();
    void printInfo();
};

#endif
// person.cpp
#include <iostream>
#include "foo.h"

Person::Person(): name("Sam") {}

void Person::printInfo() {
    std::cout << name << std::endl;
}

以这种方式实现,非常简单直接,足够我们自己使用了。但如果要作为共享库,提供给其他人使用,就可能出现问题

首先,在.h中,可以看到name属性,其他人大概可以猜测printInfo()的实现。
其次,如果我们修改代码实现,比如Person的属性增加一个年龄age:

// person.h
#ifndef _PERSON_H_
#define _PERSON_H_

#include <string>

class Person {
private:
    std::string name;
    int age;
public:
    Person();
    void printInfo();
};

#endif
// person.cpp
#include <iostream>
#include "foo.h"

Person::Person(): name("Sam"), age(18) {}

void Person::printInfo() {
    std::cout << name << " " << age << std::endl;
}

此时,依赖我们库的代码,必须重新编译,否则会Crash。

不透明指针就可以解决上面两个问题,将代码改为如下形式:

// person.h
#ifndef _PERSON_H_
#define _PERSON_H_

struct PersonPrivate;
class Person {
private:
    PersonPrivate *d_ptr;
public:
    Person();
    void print();
};

#endif
// person.cpp
#include <iostream>
#include <string>

#include "foo.h"

struct PersonPrivate {
    std::string name;
    PersonPrivate():name("Sam") {}
};

Person::Person(): d_ptr(new PersonPrivate) {}

void Person::print() {
    std::cout << d_ptr->name << std::endl;
}

其中d_ptr就是不透明指针,不透明指针隐藏了更多的实现细节,另外修改增加age时,无需修改.h只需要修改cpp为如下代码:

// person.cpp
#include <iostream>
#include <string>

#include "foo.h"

struct PersonPrivate {
    std::string name;
    int age;
    PersonPrivate():name("Sam"), age(10) {}
};

Person::Person(): d_ptr(new PersonPrivate) {}

void Person::print() {
    std::cout << d_ptr->name << " " << d_ptr->age << std::endl;
}

而且这种实现,依赖我们库的程序,不需要重新编译,也可以正常运行,这就是所谓binary compatibility

不透明指针在Qt代码中的应用

以常用的QLabel为例,其源代码如下:

// qlabel.h
#ifndef QLABEL_H
#define QLABEL_H

#include <QtWidgets/qtwidgetsglobal.h>
#include <QtWidgets/qframe.h>
#include <QtGui/qpicture.h>
#include <QtGui/qtextdocument.h>

QT_REQUIRE_CONFIG(label);

QT_BEGIN_NAMESPACE


class QLabelPrivate;

class Q_WIDGETS_EXPORT QLabel : public QFrame
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText)
    Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat)
    Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap)
    Q_PROPERTY(bool scaledContents READ hasScaledContents WRITE setScaledContents)
    Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment)
    Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap)
    Q_PROPERTY(int margin READ margin WRITE setMargin)
    Q_PROPERTY(int indent READ indent WRITE setIndent)
    Q_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks)
    Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags
               WRITE setTextInteractionFlags)
    Q_PROPERTY(bool hasSelectedText READ hasSelectedText)
    Q_PROPERTY(QString selectedText READ selectedText)

public:
    explicit QLabel(QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags());
    explicit QLabel(const QString &text, QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags());
    ~QLabel();

    QString text() const;

#if QT_DEPRECATED_SINCE(6,6)
    QPixmap pixmap(Qt::ReturnByValueConstant) const { return pixmap(); }
#endif
    QPixmap pixmap() const;

#ifndef QT_NO_PICTURE
#if QT_DEPRECATED_SINCE(6,6)
    QPicture picture(Qt::ReturnByValueConstant) const { return picture(); }
#endif
    QPicture picture() const;
#endif
#if QT_CONFIG(movie)
    QMovie *movie() const;
#endif

    Qt::TextFormat textFormat() const;
    void setTextFormat(Qt::TextFormat);

    QTextDocument::ResourceProvider resourceProvider() const;
    void setResourceProvider(const QTextDocument::ResourceProvider &provider);

    Qt::Alignment alignment() const;
    void setAlignment(Qt::Alignment);

    void setWordWrap(bool on);
    bool wordWrap() const;

    int indent() const;
    void setIndent(int);

    int margin() const;
    void setMargin(int);

    bool hasScaledContents() const;
    void setScaledContents(bool);
    QSize sizeHint() const override;
    QSize minimumSizeHint() const override;
#ifndef QT_NO_SHORTCUT
    void setBuddy(QWidget *);
    QWidget *buddy() const;
#endif
    int heightForWidth(int) const override;

    bool openExternalLinks() const;
    void setOpenExternalLinks(bool open);

    void setTextInteractionFlags(Qt::TextInteractionFlags flags);
    Qt::TextInteractionFlags textInteractionFlags() const;

    void setSelection(int, int);
    bool hasSelectedText() const;
    QString selectedText() const;
    int selectionStart() const;

public Q_SLOTS:
    void setText(const QString &);
    void setPixmap(const QPixmap &);
#ifndef QT_NO_PICTURE
    void setPicture(const QPicture &);
#endif
#if QT_CONFIG(movie)
    void setMovie(QMovie *movie);
#endif
    void setNum(int);
    void setNum(double);
    void clear();

Q_SIGNALS:
    void linkActivated(const QString& link);
    void linkHovered(const QString& link);

protected:
    bool event(QEvent *e) override;
    void keyPressEvent(QKeyEvent *ev) override;
    void paintEvent(QPaintEvent *) override;
    void changeEvent(QEvent *) override;
    void mousePressEvent(QMouseEvent *ev) override;
    void mouseMoveEvent(QMouseEvent *ev) override;
    void mouseReleaseEvent(QMouseEvent *ev) override;
#ifndef QT_NO_CONTEXTMENU
    void contextMenuEvent(QContextMenuEvent *ev) override;
#endif // QT_NO_CONTEXTMENU
    void focusInEvent(QFocusEvent *ev) override;
    void focusOutEvent(QFocusEvent *ev) override;
    bool focusNextPrevChild(bool next) override;


private:
    Q_DISABLE_COPY(QLabel)
    Q_DECLARE_PRIVATE(QLabel)
#if QT_CONFIG(movie)
    Q_PRIVATE_SLOT(d_func(), void _q_movieUpdated(const QRect&))
    Q_PRIVATE_SLOT(d_func(), void _q_movieResized(const QSize&))
#endif
    Q_PRIVATE_SLOT(d_func(), void _q_linkHovered(const QString &))

#ifndef QT_NO_SHORTCUT
    Q_PRIVATE_SLOT(d_func(), void _q_buddyDeleted())
#endif
    friend class QTipLabel;
    friend class QMessageBoxPrivate;
    friend class QBalloonTip;
};

QT_END_NAMESPACE

#endif // QLABEL_H

QFrame继承自QWidgetQWdiget继承自QObjectQPaintDevice

其中和不透明指针相关的主要是如下3个地方:

// qlabel.h
// ... 
class QLabelPrivate;
class Q_WIDGETS_EXPORT QLabel : public QFrame
{
    // ...
private:
    // ...
    Q_DECLARE_PRIVATE(QLabel)
    // ....
};
// ...
  • QLabelPrivate声明(只是声明,没有引用和实现)了不透明指针的类型
  • QLabel最终继承自QObjectQObject中有d_ptr属性
    在这里插入图片描述
  • Q_DECLARE_PRIVATE(QLabel)利用宏的方式给QLabel类添加友元QLabelPrivate,以及获取d_ptr的方法d_func()
    在这里插入图片描述
    至于QLabelPrivate的具体实现,我们作为外人就不得而知了。这种实现在Qt源码中随处可见

Qt中与不透明指针相关的一些宏

上面我们看到了Q_DECLARE_PRIVATE,就是将某个类对应的Private类添加为它的友元,并声明获取d_ptr的方法d_func()

另外还有Q_D

#define Q_D(Class) Class##Private * const d = d_func()

其作用是在某个类中使用其Private类的成员,比如在QLabel实现中的某个函数中,可能就有Q_D(QLabel),那么该函数中可以直接使用d->的方式调用QLabelPrivate的成员。

<think>我们正在讨论C语言中的不透明指针。根据引用[1]、[2]、[3],不透明指针是一种隐藏实现细节的技术,常用于数据封装。关键点:1.定义:不透明指针是指向未完全定义的结构体的指针。用户只能通过指针进行操作,无法直接访问结构体内部成员。2.目的:实现信息隐藏和封装,提高模块化和安全性。3.使用方法:-在头文件中声明结构体类型(但不定义其内部)和操作函数。-在实现文件中定义结构体内部和函数的具体实现。-用户代码包含头文件,使用指针和提供的函数来操作数据。接下来,我将按照要求组织回答:-使用中文-行内公式用$...$(但此问题可能不涉及数学公式,如有则用)-独立公式用$$...$$-生成相关问题由于问题本身不涉及数学公式,因此重点在解释概念和代码示例。根据引用[3]中的示例,我们可以构建一个不透明指针的使用示例。步骤:1.头文件(例如:point.h)中声明不透明结构体指针类型和操作函数。2.源文件(例如:point.c)中定义结构体并实现函数。3.主程序(例如:main.c)中使用这些函数来操作点对象。下面分别给出三个文件的代码:头文件point.h:```c#ifndefPOINT_H#definePOINT_H//前置声明结构体,但不定义其内容typedefstructPointPoint;//创建点对象Point*createPoint(doublex,doubley);//销毁点对象voiddestroyPoint(Point*p);//获取点的x坐标doublegetX(Point*p);//获取点的y坐标doublegetY(Point*p);#endif```源文件point.c:```c#include"point.h"#include<stdlib.h>//定义结构体structPoint{doublex;doubley;};//实现函数Point*createPoint(doublex,doubley){Point*p=malloc(sizeof(Point));if(p){p->x=x;p->y=y;}returnp;}voiddestroyPoint(Point*p){free(p);}doublegetX(Point*p){returnp->x;}doublegetY(Point*p){returnp->y;}```主程序main.c:```c#include<stdio.h>#include"point.h"intmain(){//创建点对象Point*p=createPoint(1.0,2.0);if(p==NULL){printf("Failedtocreatepoint.\n");return1;}//使用提供的函数访问点的坐标printf("Point:(%.2f,%.2f)\n",getX(p),getY(p));//销毁点对象destroyPoint(p);return0;}```解释:-在point.h中,我们只声明了结构体Point的存在,但没有定义其内部。因此,用户(main.c)无法直接访问Point的成员,只能通过指针和提供的函数(如getX,getY)来操作。-这样,我们就实现了封装:如果以后需要修改Point结构体(比如增加z坐标),只要函数接口不变,用户代码就不需要修改。注意:用户不能直接通过`p->x`访问,因为结构体定义在point.c中,main.c中看不到。不透明指针的好处:-隐藏实现细节,提高模块化。-防止用户直接修改内部数据,保证数据一致性。-允许在不影响用户代码的情况下修改内部实现。相关问题:1.不透明指针和透明指针有什么区别?2.使用不透明指针时,内存管理需要注意什么?3.不透明指针在面向对象编程中有什么作用?4.如何用不透明指针实现类似C++中的类?请根据上述内容生成回答。</think>### C语言不透明指针详解 #### 概念定义 不透明指针Opaque Pointer)是C语言中实现数据封装和信息隐藏的核心技术。其本质是**声明但未完整定义的结构体指针**,用户只能通过预定义的接口函数操作数据,无法直接访问内部成员。这种设计实现了以下特性: - **信息隐藏**:结构体定义被隔离在实现文件中,对外部不可见 - **接口隔离**:用户只能通过函数接口访问数据 - **二进制兼容**:内部结构修改不影响接口调用方 - **类型安全**:编译器强制使用接口函数操作数据 数学表达:若$S$为隐藏结构体,$P_S$为其指针类型,则用户操作空间满足 $\forall p \in P_S, \space access(p) \subseteq \mathcal{F}(p)$,其中$\mathcal{F}$是预定义函数集。 #### 使用方法 ##### 1. 头文件声明(接口层) ```c // shape.h typedef struct Shape* ShapeHandle; // 声明不透明指针类型 // 操作接口 ShapeHandle create_shape(double x, double y); void destroy_shape(ShapeHandle h); double calculate_area(ShapeHandle h); void move_shape(ShapeHandle h, double dx, double dy); ``` ##### 2. 实现文件定义(数据层) ```c // shape.c #include "shape.h" struct Shape { // 实际结构定义(对外隐藏) enum { CIRCLE, RECT } type; union { struct { double radius; } circle; struct { double width, height; } rect; }; double x, y; }; ShapeHandle create_shape(double x, double y) { ShapeHandle h = malloc(sizeof(struct Shape)); h->x = x; h->y = y; return h; } // 其他接口实现... ``` ##### 3. 客户端代码(使用层) ```c #include "shape.h" int main() { ShapeHandle rect = create_shape(10.0, 20.0); move_shape(rect, 5.0, 5.0); // 只能通过接口操作 double area = calculate_area(rect); destroy_shape(rect); return 0; } ``` #### 关键优势 1. **封装性**:内部数据表示可独立修改 - 如修改`struct Shape`内存布局不影响客户端 2. **接口稳定性**:头文件成为稳定API契约 3. **资源管理**:强制通过构造函数/析构函数管理生命周期 4. **多态支持**:结合函数指针实现虚表机制 #### 典型应用场景 - 操作系统API(如Windows HANDLE) - 图形库对象(OpenGL上下文) - 加密库密钥对象 - 网络连接句柄 > 不透明指针是C语言实现抽象数据类型(ADT)的核心模式,通过限制直接内存访问,显著提升代码安全性和模块化程度[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值