C++中的标准延迟/终结器实现是什么?

huangapple go评论106阅读模式
英文:

What is standard defer/finalizer implementation in C++?

问题

Golang风格的defer的一般概念在这里这里有解释。

我想知道,C++11、C++14的STL,或者Boost库,或者其他一些库中是否包含这样一个类的实现?这样我就可以在每个新项目中使用它,而不需要重新实现。

英文:

General idea of Golang-style defer is explained here and here.

I wonder, does STL (of C++11, C++14, ...) or maybe Boost or maybe some other library contain implementation of such a class? So I could just use it without reimplementing it in every new project.

答案1

得分: 23

这个实现与其他答案不同,它没有任何额外的开销,语法更加简洁易用。它也没有任何依赖,减少了编译时间。

你可以将这段代码片段粘贴到你的代码库中的任何地方,它就能正常工作。

#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer

用法:defer { statements; };

示例:

#include <cstdint>
#include <cstdio>
#include <cstdlib>

#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer

bool read_entire_file(char *filename, std::uint8_t *&data_out,
                      std::size_t *size_out = nullptr) {
    if (!filename)
        return false;

    auto file = std::fopen(filename, "rb");
    if (!file)
        return false;

    defer { std::fclose(file); }; // 不需要编写 RAII 文件包装器。

    if (std::fseek(file, 0, SEEK_END) != 0)
        return false;

    auto filesize = std::fpos_t{};
    if (std::fgetpos(file, &filesize) != 0 || filesize < 0)
        return false;

    auto checked_filesize = static_cast<std::uintmax_t>(filesize);
    if (checked_filesize > SIZE_MAX)
        return false;

    auto usable_filesize = static_cast<std::size_t>(checked_filesize);
    // 即使分配或读取失败,这些信息也很有用。
    if (size_out)
        *size_out = usable_filesize;

    auto memory_block = new std::uint8_t[usable_filesize];
    data_out = memory_block;
    if (memory_block == nullptr)
        return false;

    std::rewind(file);
    if (std::fread(memory_block, 1, usable_filesize, file) != usable_filesize)
        return false; // 分配成功,但读取失败。

    return true;
}

int main(int argc, char **argv) {
    if (argc < 2)
        return -1;

    std::uint8_t *file_data = nullptr;
    std::size_t file_size = 0;

    auto read_success = read_entire_file(argv[1], file_data, &file_size);

    defer { delete[] file_data; }; // 不需要编写 RAII 字符串包装器。

    if (read_success) {
        for (std::size_t i = 0; i < file_size; i += 1)
            std::printf("%c", static_cast<char>(file_data[i]));
        return 0;
    } else {
        return -1;
    }
}

附注:本地的 deferrer 对象以 zz_ 开头而不是 _,这样它就不会在调试器的 Locals 窗口中混乱,而且因为用户标识符在技术上不应以下划线开头。

英文:

This implementation is zero-overhead unlike some other answers, as well as syntactically nicer and easier to use. It also has zero dependencies, reducing compile times.

You can paste this snippet anywhere in your codebase and it will just work.

#ifndef defer
struct defer_dummy {};
template &lt;class F&gt; struct deferrer { F f; ~deferrer() { f(); } };
template &lt;class F&gt; deferrer&lt;F&gt; operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&amp;]()
#endif // defer

Usage: defer { statements; };

Example:

#include &lt;cstdint&gt;
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#ifndef defer
struct defer_dummy {};
template &lt;class F&gt; struct deferrer { F f; ~deferrer() { f(); } };
template &lt;class F&gt; deferrer&lt;F&gt; operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&amp;]()
#endif // defer
bool read_entire_file(char *filename, std::uint8_t *&amp;data_out,
std::size_t *size_out = nullptr) {
if (!filename)
return false;
auto file = std::fopen(filename, &quot;rb&quot;);
if (!file)
return false;
defer { std::fclose(file); }; // don&#39;t need to write an RAII file wrapper.
if (std::fseek(file, 0, SEEK_END) != 0)
return false;
auto filesize = std::fpos_t{};
if (std::fgetpos(file, &amp;filesize) != 0 || filesize &lt; 0)
return false;
auto checked_filesize = static_cast&lt;std::uintmax_t&gt;(filesize);
if (checked_filesize &gt; SIZE_MAX)
return false;
auto usable_filesize = static_cast&lt;std::size_t&gt;(checked_filesize);
// Even if allocation or read fails, this info is useful.
if (size_out)
*size_out = usable_filesize;
auto memory_block = new std::uint8_t[usable_filesize];
data_out = memory_block;
if (memory_block == nullptr)
return false;
std::rewind(file);
if (std::fread(memory_block, 1, usable_filesize, file) != usable_filesize)
return false; // Allocation succeeded, but read failed.
return true;
}
int main(int argc, char **argv) {
if (argc &lt; 2)
return -1;
std::uint8_t *file_data = nullptr;
std::size_t file_size = 0;
auto read_success = read_entire_file(argv[1], file_data, &amp;file_size);
defer { delete[] file_data; }; // don&#39;t need to write an RAII string wrapper.
if (read_success) {
for (std::size_t i = 0; i &lt; file_size; i += 1)
std::printf(&quot;%c&quot;, static_cast&lt;char&gt;(file_data[i]));
return 0;
} else {
return -1;
}
}

P.S.: The local deferrer object starts with zz_ and not _ so it doesn't clutter the Locals window in your debugger, and also because user identifiers technically shouldn't start with underscores.

答案2

得分: 10

有一个关于std::unique_resource_t的提案,它将使得代码如下所示:

auto file = make_unique_resource(::fopen(filename.c_str(),"w"),&::fclose);

用于资源管理,并且它定义了一个通用的scope_exit,应该与defer相同:

// 在返回之前总是说再见,
auto goodbye = make_scope_exit([&out]() -> void
{
    out << "Goodbye world..." << std::endl;
});

这个提案有可能在C++17之后的标准中被采纳。

英文:

There is a proposal for std::unique_resource_t which will enable code like

auto file=make_unique_resource(::fopen(filename.c_str(),&quot;w&quot;),&amp;::fclose);

for resources, and it defines a general scope_exit, which should be the same as defer:

// Always say goodbye before returning,
auto goodbye = make_scope_exit([&amp;out]() -&gt;void
{
out &lt;&lt; &quot;Goodbye world...&quot; &lt;&lt; std::endl;
});

It will be considered for likely adoption in the Post-C++17 standard.

答案3

得分: 6

我在 CppCon 2014 上提出了一个仅包含头文件的 Go 风格 defer 实现(YouTube 链接);我称之为 Auto。在我看来,就可教学性、效率和绝对的防错性而言,这仍然是目前最好的替代方案。在使用时,它的样子是这样的:

#include "auto.h"

int main(int argc, char **argv)
{
    Auto(std::cout << "Goodbye world" << std::endl);  // 延迟执行单个语句...
    int x[4], *p = x;
    Auto(
        if (p != x) {  // ...或者一整个代码块的控制流
            delete p;
        }
    );
    if (argc > 4) { p = new int[argc]; }
}

这个实现 的代码如下:

#pragma once

template <class Lambda> class AtScopeExit {
  Lambda& m_lambda;
public:
  AtScopeExit(Lambda& action) : m_lambda(action) {}
  ~AtScopeExit() { m_lambda(); }
};

#define Auto_INTERNAL2(lname, aname, ...) \
    auto lname = [&]() { __VA_ARGS__; }; \
    AtScopeExit<decltype(lname)> aname(lname);

#define Auto_TOKENPASTE(x, y) Auto_ ## x ## y

#define Auto_INTERNAL1(ctr, ...) \
    Auto_INTERNAL2(Auto_TOKENPASTE(func_, ctr), \
                   Auto_TOKENPASTE(instance_, ctr), __VA_ARGS__)

#define Auto(...) Auto_INTERNAL1(__COUNTER__, __VA_ARGS__)

是的,这就是整个文件:只有 15 行代码!它需要 C++11 或更新版本,并且需要编译器支持 __COUNTER__(尽管如果需要在某些不支持 __COUNTER__ 的编译器上实现可移植性,可以使用 __LINE__ 作为替代)。至于效率,我从未见过 GCC 或 Clang 为任何 -O2 或更高级别的 Auto 使用生成除了完美代码之外的任何东西 —— 它是那种传说中的“零成本抽象”。

原始来源 还有一个适用于 GCC 的 C89 版本,利用了一些非常特定于 GCC 的属性。

英文:

I presented a header-only implementation of Go-style defer at CppCon 2014 (YouTube link); I called it Auto. IMHO this is still far and away the best alternative out there in terms of teachability, efficiency, and absolute fool-proofness. In use, it looks like this:

#include &quot;auto.h&quot;
int main(int argc, char **argv)
{
Auto(std::cout &lt;&lt; &quot;Goodbye world&quot; &lt;&lt; std::endl);  // defer a single statement...
int x[4], *p = x;
Auto(
if (p != x) {  // ...or a whole block&#39;s worth of control flow
delete p;
}
);
if (argc &gt; 4) { p = new int[argc]; }
}

The implementation looks like this:

#pragma once
template &lt;class Lambda&gt; class AtScopeExit {
Lambda&amp; m_lambda;
public:
AtScopeExit(Lambda&amp; action) : m_lambda(action) {}
~AtScopeExit() { m_lambda(); }
};
#define Auto_INTERNAL2(lname, aname, ...) \
auto lname = [&amp;]() { __VA_ARGS__; }; \
AtScopeExit&lt;decltype(lname)&gt; aname(lname);
#define Auto_TOKENPASTE(x, y) Auto_ ## x ## y
#define Auto_INTERNAL1(ctr, ...) \
Auto_INTERNAL2(Auto_TOKENPASTE(func_, ctr), \
Auto_TOKENPASTE(instance_, ctr), __VA_ARGS__)
#define Auto(...) Auto_INTERNAL1(__COUNTER__, __VA_ARGS__)

Yes, that's the entire file: just 15 lines of code! It requires C++11 or newer, and requires your compiler to support __COUNTER__ (although you can use __LINE__ as a poor man's __COUNTER__ if you need portability to some compiler that doesn't support it). As for efficiency, I've never seen GCC or Clang generate anything other than perfect code for any use of Auto at -O2 or higher — it's one of those fabled "zero-cost abstractions."

The original source also has a C89 version that works on GCC by exploiting some very GCC-specific attributes.

答案4

得分: 2

这是我的解决方案,与你在Swift中遇到的类型相似,但我没有处理任何异常(如果需要,可以很容易地添加,只需像在PSIAlt的解决方案中一样使用try/catch块):

class Defer {
    using F = std::function<void(void)>;
    std::vector<F> funcs;
    void add(F f) {
        funcs.push_back(f);
    }
public:
    Defer(F f) { add(f); }
    Defer() {}
    Defer(const Defer& ) = delete;
    Defer& operator= (const Defer& ) = delete;
    
    void operator() (F f) { add(f); }
    ~Defer() {
        for(;!funcs.empty();) {
            funcs.back()();
            funcs.pop_back();
        }
    }
};

由于它使用了vector,可能看起来有些笨重,但它保留了Swift中defer的行为,即以相反的顺序调用函数:

Defer defer([]{std::cout << "fourth" << std::endl;});
std::cout << "first" << std::endl;
auto s = "third";
defer([&s]{std::cout << s << std::endl;});
std::cout << "second" << std::endl;

但它比defer更强大,因为你可以在任何比自己更低的作用域中添加延迟调用。只需注意捕获的lambda在使用时不会超出作用域(这是一个小缺陷,但如果你小心的话不是一个严重的问题)。

通常情况下,我不会使用这个,除非它将成为一次性语句。理想情况下,你应该将任何资源封装在一个新的类中,在其作用域结束时释放资源,但如果是顶层代码,涉及大量资源和错误处理,从代码可读性的角度来看,defer确实更有意义。你不需要记住一堆实际上都做同样事情的新类。

英文:

Here's my solution, which is similar to the type you'd encounter in swift, but I don't handle any exceptions (easy enough to add if required, just use a try/catch block like in PSIAlt's solution):

class Defer {
using F = std::function&lt;void(void)&gt;;
std::vector&lt;F&gt; funcs;
void add(F f) {
funcs.push_back(f);
}
public:
Defer(F f) { add(f); }
Defer() {}
Defer(const Defer&amp; ) = delete;
Defer&amp; operator= (const Defer&amp; ) = delete;
void operator() (F f) { add(f); }
~Defer() {
for(;!funcs.empty();) {
funcs.back()();
funcs.pop_back();
}
}
};

It may seem clunky due to its use of vector, but it retains the behavior of swift's defer where the functions are called in reverse order:

Defer defer([]{std::cout &lt;&lt; &quot;fourth&quot; &lt;&lt; std::endl;});
std::cout &lt;&lt; &quot;first&quot; &lt;&lt; std::endl;
auto s = &quot;third&quot;;
defer([&amp;s]{std::cout &lt;&lt; s &lt;&lt; std::endl;});
std::cout &lt;&lt; &quot;second&quot; &lt;&lt; std::endl;

But it's a bit more powerful than defer in that you can add deferred calls at any scope lower than your own. You just have to be careful that the lambda captured doesn't go out of scope when you use this (a bit of a flaw but not a serious one if you're careful).

I normally wouldn't use this, unless it's going to be a one-off statement. Ideally you'd wrap any resource around a new class that deallocates it when it goes out of scope, but if its top-level code with lots of resources and error handling, defer does make more sense from a code readability standpoint. You don't have to remember a bunch of new classes that really all do the same thing.

答案5

得分: 1

libgolang 提供了 defer 的实现(commit 123)。使用方法如下:

void myfunc() {
      defer([]() {
          printf("离开...\n");
      });

      ...
}

defer 本身的实现如下:

// defer(f) 模仿了 golang 中的 `defer f()`。
// 注意:与 Go 不同,f 在当前作用域结束时调用,而不是函数。
#define defer(f) golang::_deferred _defer_(__COUNTER__) (f)
#define _defer_(counter)    _defer_2(counter)
#define _defer_2(counter)   _defer_##counter
struct _deferred {
    typedef func<void()> F;
    F f;
    _deferred(F f) : f(f) {}
    ~_deferred() { f(); }
private:
    _deferred(const _deferred&);    // 不要复制
    _deferred(_deferred&&);         // 不要移动
};
英文:

libgolang provides defer implementation (commit 1, 2, 3). Usage is like:

void myfunc() {
      defer([]() {
          printf(&quot;leaving...\n&quot;);
      });

      ...
}

defer itself is implemented as:

// defer(f) mimics `defer f()` from golang.
// NOTE contrary to Go f is called at end of current scope, not function.
#define defer(f) golang::_deferred _defer_(__COUNTER__) (f)
#define _defer_(counter)    _defer_2(counter)
#define _defer_2(counter)   _defer_##counter
struct _deferred {
    typedef func&lt;void()&gt; F;
    F f;
    _deferred(F f) : f(f) {}
    ~_deferred() { f(); }
private:
    _deferred(const _deferred&amp;);    // don&#39;t copy
    _deferred(_deferred&amp;&amp;);         // don&#39;t move
};

答案6

得分: 0

在新标准出现之前,我使用简单的RAII类来实现这个功能:

struct ScopeGuard {
    typedef std::function<void()> func_type;
    explicit ScopeGuard(func_type _func) : func(_func), isReleased(false) {}
    ~ScopeGuard() {
        if (!isReleased && func) try {
            func();
        } catch (...) {};
    }
    void Forget() { isReleased = true; }

    //noncopyable
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;

private:
    func_type func;
    bool isReleased;
};

之后,它可以用于任何事情,例如:

FILE *fp = fopen(filename.c_str(), "w");
if (fp == NULL) throw invalid_argument();
ScopeGuard _fp_guard([&fp]() {
    fclose(fp);
});

另外,你也可以使用Boost.ScopeExit和类似的实现方式。

英文:

Before new standard comes, I use simple RAII class for this:

struct ScopeGuard {
typedef std::function&lt; void() &gt; func_type;
explicit ScopeGuard(func_type _func) : func(_func), isReleased(false) {}
~ScopeGuard() {
if( !isReleased &amp;&amp; func ) try {
func();
}catch(...) {};
}
void Forget() { isReleased=true; }
//noncopyable
ScopeGuard(const ScopeGuard&amp;) = delete;
ScopeGuard&amp; operator=(const ScopeGuard&amp;) = delete;
private:
func_type func;
bool isReleased;
};

Later it can be used for any things, for example:

FILE *fp = fopen(filename.c_str(),&quot;w&quot;);
if(fp==NULL) throw invalid_argument();
ScopeGuard _fp_guard([&amp;fp]() {
fclose(fp);
});

Also, you can use Boost.ScopeExit and similar implementations.

答案7

得分: 0

这是我的延迟执行实现,但没有 noexcept 保证,我仍然认为这不是一个很好的实现。

使用方法如下:

#include <iostream>
#include "defer.hpp"
using namespace std;
int main() {
defer []{cout << "defered" << endl;};
}

实现代码如下:

#define DEFER_CONCAT_IMPL(x, y) x##y
#define DEFER_CONCAT(x, y) DEFER_CONCAT_IMPL(x, y)
#define AUTO_DEFER_VAR DEFER_CONCAT(__defer, __LINE__)
#define defer ::__defer AUTO_DEFER_VAR; AUTO_DEFER_VAR-
class __defer {
public:
template<typename Callable>
void operator- (Callable&& callable) {
defer_ = std::forward<Callable>(callable);
}
~__defer() {
defer_();
}
private:
std::function<void(void)> defer_;
};
英文:

Here is my defer implementation, but without noexcept guarantee, I still don't think it is very good implementation.

Used like this:

#include &lt;iostream&gt;
#include &quot;defer.hpp&quot;
using namespace std;
int main() {
defer []{cout &lt;&lt; &quot;defered&quot; &lt;&lt; endl;};
}

Implementation:

#define DEFER_CONCAT_IMPL(x, y) x##y
#define DEFER_CONCAT(x, y) DEFER_CONCAT_IMPL(x, y)
#define AUTO_DEFER_VAR DEFER_CONCAT(__defer, __LINE__)
#define defer ::__defer AUTO_DEFER_VAR; AUTO_DEFER_VAR-
class __defer {
public:
template&lt;typename Callable&gt;
void operator- (Callable&amp;&amp; callable) {
defer_ = std::forward&lt;Callable&gt;(callable);
}
~__defer() {
defer_();
}
private:
std::function&lt;void(void)&gt; defer_;
};

答案8

得分: 0

我们在这里不应该使用std::function,因为创建std::function对象的速度相对较慢。

这里是一个使用lambda而不是std::function的C++11实现。编译器将内联lambda,我们将获得最佳性能。

#include <utility>

template <typename F>
struct _defer_class {
    _defer_class(F&& f) : _f(std::forward<F>(f)) {}
    ~_defer_class() { _f(); }
    typename std::remove_reference<F>::type _f;
};

template <typename F>
inline _defer_class<F> _create_defer_class(F&& f) {
    return _defer_class<F>(std::forward<F>(f));
}

#define _defer_name_cat(x, n) x##n
#define _defer_name(x, n) _defer_name_cat(x, n)
#define _defer_var_name _defer_name(_defer_var_, __LINE__)

#define defer(e) \
    auto _defer_var_name = _create_defer_class([&](){ e; })

我们可以像这样使用它:

#include "co/defer.h"
#include "co/log.h"

void f(int x, int y) {
    COUT << (x + y);
}

int main(int argc, char** argv) {
    defer(COUT << "hello world");
    defer(COUT << "hello again");
    defer(f(1, 1); f(1, 3));
    return 0;
}
英文:

We should not use std::function here, as creating objects of std::function is rather slow.

Here is an implement in C++11 using lambda instead of std::function. Compilers will inline the lambda and we'll get the best performance.

#include &lt;utility&gt;
template &lt;typename F&gt;
struct _defer_class {
_defer_class(F&amp;&amp; f) : _f(std::forward&lt;F&gt;(f)) {}
~_defer_class() { _f(); }
typename std::remove_reference&lt;F&gt;::type _f;
};
template &lt;typename F&gt;
inline _defer_class&lt;F&gt; _create_defer_class(F&amp;&amp; f) {
return _defer_class&lt;F&gt;(std::forward&lt;F&gt;(f));
}
#define _defer_name_cat(x, n) x##n
#define _defer_name(x, n) _defer_name_cat(x, n)
#define _defer_var_name _defer_name(_defer_var_, __LINE__)
#define defer(e) \
auto _defer_var_name = _create_defer_class([&amp;](){ e; })

We can use it like this:

#include &quot;co/defer.h&quot;
#include &quot;co/log.h&quot;
void f(int x, int y) {
COUT &lt;&lt; (x + y);
}
int main(int argc, char** argv) {
defer(COUT &lt;&lt; &quot;hello world&quot;);
defer(COUT &lt;&lt; &quot;hello again&quot;);
defer(f(1, 1); f(1, 3));
return 0;
}

答案9

得分: -1

使用如下方式:

int main() {
    int age = 20;
    DEFER { std::cout << "age = " << age << std::endl; };
    DEFER { std::cout << "I'll be first\n"; };
}

我的实现(请自行添加头文件):

class ScopeExit {
public:
    ScopeExit() = default;

    template <typename F, typename... Args>
    ScopeExit(F&& f, Args&&... args) {
        // 绑定所有参数,使参数列表为空
        auto temp = std::bind(std::forward<F>(f), std::forward<Args>(args)...);

        // 丢弃返回类型,使返回类型为 void,符合 func_ 的类型
        func_ = [temp]() { (void)temp(); };
    }

    ScopeExit(ScopeExit&& r) {
        func_ = std::move(r.func_);
    }

    // 析构函数,执行延迟的函数
    ~ScopeExit() {
        if (func_)
            func_();
    }

private:
    std::function<void()> func_;
};

// 丑陋的宏,辅助使用
#define CONCAT(a, b) a##b

#define DEFER _MAKE_DEFER_HELPER_(__LINE__)
#define _MAKE_DEFER_HELPER_(line) ScopeExit CONCAT(_defer, line) = [&]() 
英文:

used like this:

int main()   {
int  age = 20;
DEFER { std::cout &lt;&lt; &quot;age = &quot; &lt;&lt; age &lt;&lt; std::endl; };
DEFER { std::cout &lt;&lt; &quot;I&#39;ll be first\n&quot;; };
}

My implementation( please add header files yourself):

 class ScopeExit
{
public:
ScopeExit() = default;
template &lt;typename F, typename... Args&gt;
ScopeExit(F&amp;&amp; f, Args&amp;&amp;... args)
{
// Bind all args, make args list empty
auto temp = std::bind(std::forward&lt;F&gt;(f), std::forward&lt;Args&gt;(args)...);
// Discard return type, make return type = void, conform to func_ type
func_ = [temp]() { (void)temp(); };
}
ScopeExit(ScopeExit&amp;&amp; r)
{
func_ = std::move(r.func_);
}
// Destructor and execute defered function
~ScopeExit()
{
if (func_)
func_();
}
private:
std::function&lt; void ()&gt; func_;
};
// Ugly macro, help
#define CONCAT(a, b) a##b
#define DEFER  _MAKE_DEFER_HELPER_(__LINE__)
#define _MAKE_DEFER_HELPER_(line)   ScopeExit    CONCAT(_defer, line) = [&amp;] () 

huangapple
  • 本文由 发表于 2015年9月7日 14:35:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/32432450.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定