c++ - 错误:在静态成员函数中无效使用成员 XXX
问题描述
我一直在试图弄清楚如何QStringList
在我的成员函数之间传递指针。出于某种原因,我收到一条错误消息,内容如下:
mainwindow.cpp:497:错误:在静态成员函数中无效使用成员“MainWindow::emails”
// global definition
QStringList* MainWindow::emails;
主窗口.h
class MainWindow : public QMainWindow{
...
private:
static QStringList * emails;
}
主窗口.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
size_t MainWindow::WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
QString filteredNewLine = plainText.replace("\n"," ");
QRegularExpression re("[a-z0-9]+[_a-z0-9.-]*[a-z0-9]+@[a-z0-9-]+(.[a-z0-9-]+)");
QRegularExpressionMatchIterator i = re.globalMatch(filteredNewLine);
QStringList words;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString word = match.captured(0);
words << word;
MainWindow::emails = &words;
qDebug() << MainWindow::emails
}
}
MainWindow::~MainWindow()
{
delete emails;
}
解决方案
您似乎在项目中使用 curl。curl 有一个 C++ 绑定,但我们可以创建一个利用 Qt 的绑定。
首先,让我们将全局初始化和清理包装在 RAII 包装器中:
// https://github.com/KubaO/stackoverflown/tree/master/questions/curl-50975613
#include <QtWidgets>
#include <curl/curl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cstdio>
#include <limits>
class CurlGlobal {
Q_DISABLE_COPY(CurlGlobal)
CURLcode rc;
public:
CurlGlobal() { rc = curl_global_init(CURL_GLOBAL_ALL); }
~CurlGlobal() { curl_global_cleanup(); }
CURLcode code() const { return rc; }
explicit operator bool() const { return rc == CURLE_OK; }
};
我们还需要一个 RAII 包装器curl_slist
:
class CurlStringList {
struct curl_slist *list = {};
public:
CurlStringList() = default;
explicit CurlStringList(const QStringList &strings) {
for (auto &s : strings)
list = curl_slist_append(list, s.toLatin1().constData());
}
CurlStringList &operator=(CurlStringList &&o) {
std::swap(o.list, list);
return *this;
}
~CurlStringList() {
curl_slist_free_all(list);
}
struct curl_slist *curl() const { return list; }
};
“简单”的句柄 ,CURL*
可以包裹在 中QObject
,以利用其灵活性。这是我们看到如何在CURLOPT_xxxDATA
没有任何全局/静态变量的情况下使用正确实现回调的地方:
class CurlEasy : public QObject {
Q_OBJECT
friend class CurlMulti;
CURL *const d = curl_easy_init();
QAtomicInteger<bool> inMulti = false;
CURLcode rc = d ? CURLE_OK : (CURLcode)-1;
char err[CURL_ERROR_SIZE];
CurlStringList headers;
bool addToMulti();
void removeFromMulti();
static size_t write_callback(const char *ptr, size_t size, size_t nmemb, void *data) {
Q_ASSERT(data);
return static_cast<CurlEasy*>(data)->writeCallback(ptr, size * nmemb);
}
protected:
virtual qint64 writeCallback(const char *, qint64) {
return -1;
}
void setWriteCallback() {
curl_easy_setopt(d, CURLOPT_WRITEDATA, (void*)this);
curl_easy_setopt(d, CURLOPT_WRITEFUNCTION, (void*)write_callback);
}
virtual void clear() {}
该clear
方法在派生类中用于在请求开始之前准备状态。
其余方法包装并公开各种选项和错误处理:
public:
CurlEasy(QObject *parent = {}) : QObject(parent)
{
curl_easy_setopt(d, CURLOPT_ERRORBUFFER, err);
}
~CurlEasy() override {
removeFromMulti();
curl_easy_cleanup(d);
}
CURL *c() const { return d; }
operator CURL*() const { return d; }
bool ok() const { return rc == CURLE_OK; }
QString errorString() const { return QString::fromUtf8(err); }
bool setUrl(const QUrl& url) {
rc = curl_easy_setopt(d, CURLOPT_URL, url.toEncoded().constData());
return ok();
}
void setMaxRedirects(int count) {
curl_easy_setopt(d, CURLOPT_FOLLOWLOCATION, count ? 1L : 0L);
curl_easy_setopt(d, CURLOPT_MAXREDIRS, (long)count);
}
void setVerbose(bool v) {
curl_easy_setopt(d, CURLOPT_VERBOSE, v ? 1L : 0L);
}
bool get() {
if (inMulti.loadAcquire())
return false;
clear();
curl_easy_setopt(d, CURLOPT_HTTPGET, 1L);
if (addToMulti())
return true;
rc = curl_easy_perform(d);
if (!ok())
qDebug() << errorString();
return ok();
}
void setHeaders(const QStringList &h) {
headers = CurlStringList(h);
curl_easy_setopt(d, CURLOPT_HTTPHEADER, headers.curl());
}
void setWriteTo(FILE *);
void setReadFrom(FILE *);
void setWriteTo(QIODevice *dev);
void setReadFrom(QIODevice *dev);
};
setWriteTo
和方法的细节setReadFrom
可以跳过:它们用于使CurlEasy
对象与 C 文件和 Qt I/O 设备互操作。请参阅存储库以获取完整代码。
现在,我们需要一个行解析器:一个使用writeCallback
解码单个行的类:
class CurlLineParser : public CurlEasy {
Q_OBJECT
QByteArray m_buf;
protected:
qint64 writeCallback(const char *ptr, qint64 count) override {
const char *start = ptr;
const char *const end = ptr + count;
for (; ptr != end; ptr++) {
if (*ptr == '\n') {
if (!m_buf.isEmpty())
m_buf.append(start, ptr-start);
else
m_buf = QByteArray::fromRawData(start, ptr-start);
emit hasLine(QString::fromUtf8(m_buf));
m_buf.clear();
start = ptr + 1;
}
}
// keep partial line
m_buf = QByteArray::fromRawData(start, ptr-start);
return count;
}
void clear() override {
m_buf.clear();
}
public:
CurlLineParser(QObject *parent = {}) : CurlEasy(parent) {
setWriteCallback();
}
Q_SIGNAL void hasLine(const QString &);
};
可以通过监听hasLine
信号来实现线路的特定应用过滤。
此时,我们拥有运行同步(阻塞)请求所需的一切。让我们暂时推迟CurlMulti
类的检查,看看用户界面/演示的样子:
class Ui : public QWidget {
Q_OBJECT
CurlLineParser m_curl{this};
QRegularExpression m_re{"[a-z0-9]+[_a-z0-9.-]*[a-z0-9]+@[a-z0-9-]+(.[a-z0-9-]+)"};
QStringList m_emails;
QGridLayout m_layout{this};
QPlainTextEdit m_view;
QLineEdit m_url;
QCheckBox m_async{"Async"};
QPushButton m_get{"Get"};
public:
Ui() {
m_url.setPlaceholderText("Url");
m_view.setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_url.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_layout.addWidget(&m_view, 0, 0, 1, 3);
m_layout.addWidget(&m_url, 1, 0);
m_layout.addWidget(&m_async, 1, 1);
m_layout.addWidget(&m_get, 1, 2);
connect(&m_get, &QPushButton::clicked, this, [this]{
m_emails.clear();
emit requestGet(m_url.text());
});
connect(&m_async, &QCheckBox::toggled, this, &Ui::requestAsync);
}
Q_SIGNAL void requestGet(const QString &);
Q_SIGNAL void requestAsync(bool);
void setUrl(const QString &url) { m_url.setText(url); }
void setAsync(bool async) { m_async.setChecked(async); }
void processLine(const QString &line) {
auto it = m_re.globalMatch(line);
while (it.hasNext()) {
auto email = it.next().captured(0);
m_emails.push_back(email);
m_view.appendPlainText(email);
}
}
};
有Ui
一种方法可以处理来自任何来源的行,并使用正则表达式对其进行过滤。这个类与 curl 机器的细节完全解耦。
curl "stack" 和 Ui 之间的连接通过以下方式完成main
:
int main(int argc, char* argv[]) {
CurlGlobal curlGlobal;
QApplication app(argc, argv);
QThread netThread;
CurlMulti multi;
CurlLineParser curl;
multi.moveToThread(&netThread);
Ui ui;
QObject::connect(&ui, &Ui::requestGet, [&](const QString &url){
curl.setUrl(url);
curl.get();
});
QObject::connect(&ui, &Ui::requestAsync, [&](bool async){
QMetaObject::invokeMethod(&curl, [&curl, &multi, async]{
if (async) curl.moveToThread(multi.thread());
curl.setParent(async ? &multi : nullptr);
if (!async) curl.moveToThread(qApp->thread());
});
});
QObject::connect(&curl, &CurlLineParser::hasLine, &ui, &Ui::processLine);
curl.setMaxRedirects(2);
curl.setVerbose(false);
curl.setHeaders({"Accept: text/plain; charset=utf-8"});
ui.setUrl("https://gist.github.com/retronym/f9419787089149ad3a59835c2e1ab81a/"
"raw/8cd88ab3645508ae1243f3aa4ec7c012af4fae7b/emails.txt");
ui.show();
netThread.start();
int rc = app.exec();
QMetaObject::invokeMethod(&multi, [&]{
if (!curl.parent())
curl.moveToThread(app.thread());
multi.moveToThread(app.thread());
netThread.quit();
});
netThread.wait();
return rc;
}
#include "main.moc"
When the asynchronous option is selected, the CurlLineParser
object is moved to a worker thread, and made a child of the CurlMulti
object. 线程争吵是必要的,只是因为可以在阻塞、相同线程实现和异步、单独线程实现之间翻转配置。当然也可以进行异步、同线程配置 - 只需删除所有moveToThread
调用。
CurlMulti
环绕并使用 Qt的CURLM*
套接字通知器将 curl 与事件循环接口。每个套接字的通知器存储为每个套接字的指针,使用curl_multi_assign
.
class CurlMulti : public QObject {
Q_OBJECT
friend class CurlEasy;
struct Notifiers {
QScopedPointer<QSocketNotifier> read, write, exception;
};
CURLM *m = curl_multi_init();
QBasicTimer m_timer;
void timerEvent(QTimerEvent *ev) override {
int running;
if (ev->timerId() == m_timer.timerId()) {
m_timer.stop();
curl_multi_socket_action(m, CURL_SOCKET_TIMEOUT, 0, &running);
processInfo();
}
}
static int socket_callback(CURL *, curl_socket_t s, int what, void *userp, void *socketp) {
Q_ASSERT(userp);
return static_cast<CurlMulti*>(userp)->
socketCallback(s, what, static_cast<Notifiers*>(socketp));
}
static int timer_callback(CURLM *, long ms, void *userp) {
Q_ASSERT(userp);
auto *q = static_cast<CurlMulti*>(userp);
if (ms == -1)
q->m_timer.stop();
else if (ms >= 0)
q->m_timer.start(ms, q);
return 0;
}
int socketCallback(curl_socket_t s, int what, Notifiers* n) {
if (what == CURL_POLL_REMOVE) {
delete n;
curl_multi_assign(m, s, nullptr);
processInfo();
return 0;
}
if (!n) {
n = new Notifiers;
curl_multi_assign(m, s, n);
n->exception.reset(new QSocketNotifier(s, QSocketNotifier::Exception, this));
connect(&*n->exception, &QSocketNotifier::activated, [this, s]{
int running;
curl_multi_socket_action(m, s, CURL_CSELECT_ERR, &running);
});
}
if ((what & CURL_POLL_IN) && !n->read) {
n->read.reset(new QSocketNotifier(s, QSocketNotifier::Read, this));
connect(&*n->read, &QSocketNotifier::activated, [this, s]{
int running;
curl_multi_socket_action(m, s, CURL_CSELECT_IN, &running);
});
}
if ((what & CURL_POLL_OUT) && !n->write) {
n->write.reset(new QSocketNotifier(s, QSocketNotifier::Write, this));
connect(&*n->write, &QSocketNotifier::activated, [this, s]{
int running;
curl_multi_socket_action(m, s, CURL_CSELECT_OUT, &running);
});
}
n->exception->setEnabled(what & CURL_POLL_INOUT);
if (n->read)
n->read->setEnabled(what & CURL_POLL_IN);
if (n->write)
n->write->setEnabled(what & CURL_POLL_OUT);
processInfo();
return 0;
}
void processInfo() {
int msgq;
while (const auto *info = curl_multi_info_read(m, &msgq)) {
if (info->msg == CURLMSG_DONE)
for (auto *c : children())
if (auto *b = qobject_cast<CurlEasy*>(c))
if (b->d == info->easy_handle && b->inMulti)
b->removeFromMulti();
}
}
public:
CurlMulti(QObject *parent = {}) : QObject(parent) {
curl_multi_setopt(m, CURLMOPT_SOCKETDATA, (void*)this);
curl_multi_setopt(m, CURLMOPT_SOCKETFUNCTION, (void*)socket_callback);
curl_multi_setopt(m, CURLMOPT_TIMERDATA, (void*)this);
curl_multi_setopt(m, CURLMOPT_TIMERFUNCTION, (void*)timer_callback);
int running;
curl_multi_socket_action(m, CURL_SOCKET_TIMEOUT, 0, &running);
}
Q_SIGNAL void finished(CurlEasy *);
~CurlMulti() override {
for (auto *c : children())
if (auto *b = qobject_cast<CurlEasy*>(c))
b->removeFromMulti();
curl_multi_cleanup(m);
}
};
最后,两个单独定义的内部方法CurlEasy
处理对象的添加和删除multi
:
bool CurlEasy::addToMulti() {
auto *m = qobject_cast<CurlMulti*>(parent());
if (d && m && inMulti.testAndSetOrdered(false, true)) {
QMetaObject::invokeMethod(m, [m, this]{
curl_multi_add_handle(m->m, d);
});
}
return inMulti;
}
void CurlEasy::removeFromMulti() {
auto *m = qobject_cast<CurlMulti*>(parent());
if (d && m && inMulti.testAndSetOrdered(true, false)) {
curl_multi_remove_handle(m->m, d);
emit m->finished(this);
}
}
也可以看看:
推荐阅读
- python - Pandas:使用 loc 将一行布尔值附加到 df 更改为 int
- sql - 查询以查找要聚合的不同字符串
- javascript - 检查用户和机器人角色是否高于提到的用户(discord.js)
- android - 使用 PassportJS 响应本机社交登录
- ios - Apple Silicon M1 Mac Mini 上的 Flutter:iOS SIM 错误
- scala - 杰克逊用 $ 符号读取节点字段
- npm - 为什么每次我使用 npm 或 yarn 安装包时,它都会安装所有依赖项?
- rust - 迭代器表示任意行为
- javascript - IOS SAP Hybrid/Native 移动应用程序中的 ajax GET 调用中的禁止错误
- python - NN 如何理解哪个类对应于哪个输出节点?反过来,我们作为人们如何识别概率对应的内容?