如何避免在调试宏中移动构造函数
How to avoid the move constructor in debug macros?
我正在尝试编写一个调试宏/模板,该宏/模板输出函数名称,后跟返回值,然后返回该值。我提出了4个makros:RETURN
用于基本类型,RETURN_P
用于std包装器中的指针(uniqe_ptr,shared_ptr,可选),RETURN_B
用于布尔值,RETURN_A
用于我只能显示地址的东西。
为了测试宏,我编写了一个Resource
类,它是可移动的,但不可复制,并且有一个"私有"构造函数。我使用私有访问参数来防止任何人调用构造函数,同时保留使用std::make_unique
等转发函数的能力。这是一个测试对象,所以它为我迄今为止遇到的所有类型提供了工厂。对于每种类型,都有一个工厂作为makro调用的一部分创建该类型,还有一个工厂首先创建一个临时变量,然后用它调用makro
最后,main()
函数创建了许多Resource
对象来测试所有的makros,包括int和bool基本类型。
现在是我的问题:
- 使用定义的NDEBUG,
alloc_opt2
和create2
方法调用Resource的move构造函数。这在某种程度上可以避免吗 - 当未定义NDEBUG时,
alloc_opt
和create
方法也会调用Resource的move构造函数。我真的很想至少取消这些动作,这样无论有没有NDEBUG,行为都是一样的
有什么可以做的吗?
#include <memory>
#include <tuple>
#include <experimental/optional>
#include <cassert>
#include <stdio.h>
class Resource {
struct access { };
public:
static Resource * alloc_ptr();
static Resource * alloc_ptr2();
static std::unique_ptr<Resource> alloc_uniqe();
static std::unique_ptr<Resource> alloc_uniqe2();
static std::shared_ptr<Resource> alloc_shared();
static std::shared_ptr<Resource> alloc_shared2();
static std::experimental::optional<Resource> alloc_opt();
static std::experimental::optional<Resource> alloc_opt2();
static Resource create();
static Resource create2();
Resource(access);
~Resource();
Resource(Resource &&other);
operator bool() const;
private:
Resource(const Resource &) = delete;
Resource & operator =(const Resource &) = delete;
bool valid;
};
#define NDEBUG
#ifdef NDEBUG
#define RETURN(format, arg) return arg;
#define RETURN_P(arg) return arg;
#define RETURN_B(arg) return arg;
#define RETURN_A(arg) return arg;
#else
template <class T>
T debug(const char *format, const char *name, T && t) {
fprintf(stderr, "[<T>] ");
fprintf(stderr, format, name, t);
return std::forward<T>(t);
}
template <class T>
T debug_p(const char *format, const char *name, T && t) {
fprintf(stderr, "[ptr<T>] ");
fprintf(stderr, format, name, t ? &*t : nullptr);
return std::forward<T>(t);
}
bool debug_b(const char *format, const char *name, bool t) {
fprintf(stderr, "[bool] ");
fprintf(stderr, format, name, t ? "true" : "false");
return t;
}
template <class T>
T debug_a(const char *format, const char *name, T && t) {
fprintf(stderr, "[&<T>] ");
fprintf(stderr, format, name, &t);
return std::forward<T>(t);
}
#define RETURN(format, arg) return debug("%s : " format "n", __PRETTY_FUNCTION__, arg);
#define RETURN_P(arg) return debug_p("%s = %pn", __PRETTY_FUNCTION__, arg);
#define RETURN_B(arg) return debug_b("%s = %sn", __PRETTY_FUNCTION__, arg);
#define RETURN_A(arg) return debug_a("%s = %pn", __PRETTY_FUNCTION__, arg);
#endif
Resource * Resource::alloc_ptr() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN_P(new Resource(access{}));
}
Resource * Resource::alloc_ptr2() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
Resource * r = new Resource(access{});
RETURN_P(r);
}
std::unique_ptr<Resource> Resource::alloc_uniqe() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN_P(std::make_unique<Resource>(access{}));
}
std::unique_ptr<Resource> Resource::alloc_uniqe2() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
std::unique_ptr<Resource> r = std::make_unique<Resource>(access{});
RETURN_P(std::move(r));
}
std::shared_ptr<Resource> Resource::alloc_shared() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN_P(std::make_shared<Resource>(access{}));
}
std::shared_ptr<Resource> Resource::alloc_shared2() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
std::shared_ptr<Resource> r = std::make_shared<Resource>(access{});
RETURN_P(r);
}
std::experimental::optional<Resource> Resource::alloc_opt() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN_P(std::experimental::optional<Resource>(std::experimental::in_place, access{}));
}
std::experimental::optional<Resource> Resource::alloc_opt2() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
std::experimental::optional<Resource> r = std::experimental::optional<Resource>(std::experimental::in_place, access{});
RETURN_P(std::move(r));
}
Resource Resource::create() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN_A(Resource(access{}));
}
Resource Resource::create2() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
Resource r = Resource(access{});
RETURN_A(std::move(r));
}
Resource::Resource(access) : valid(true) {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
}
Resource::~Resource() {
fprintf(stderr, "%s [%s]n", __PRETTY_FUNCTION__,
valid ? "valid" : "invalid");
valid = false;
}
Resource::Resource(Resource &&other) : valid(other.valid) {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
assert(other.valid);
other.valid = false;
}
Resource::operator bool() const {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN_B(valid);
}
int test_int() {
fprintf(stderr, "%sn", __PRETTY_FUNCTION__);
RETURN("%d", 1);
}
int main() {
fprintf(stderr, "### test test_int()n");
{
int i = test_int();
fprintf(stderr, "### test test_int() createdn");
if (i) { }
}
fprintf(stderr, "### test test_int() donenn");
fprintf(stderr, "### test Resource::alloc_ptr()n");
{
Resource * r1 = Resource::alloc_ptr();
fprintf(stderr, "### test Resource::alloc_ptr() allocatedn");
delete r1;
}
fprintf(stderr, "### test Resource::alloc_ptr() donenn");
fprintf(stderr, "### test Resource::alloc_ptr2()n");
{
Resource * r1 = Resource::alloc_ptr2();
fprintf(stderr, "### test Resource::alloc_ptr2() allocatedn");
delete r1;
}
fprintf(stderr, "### test Resource::alloc_ptr2() donenn");
fprintf(stderr, "### test Resource::alloc_unique()n");
{
std::unique_ptr<Resource> r2 = Resource::alloc_uniqe();
fprintf(stderr, "### test Resource::alloc_uniqe() allocatedn");
if (r2) { }
}
fprintf(stderr, "### test Resource::alloc_uniqe() donenn");
fprintf(stderr, "### test Resource::alloc_unique2()n");
{
std::unique_ptr<Resource> r2 = Resource::alloc_uniqe2();
fprintf(stderr, "### test Resource::alloc_uniqe2() allocatedn");
if (r2) { }
}
fprintf(stderr, "### test Resource::alloc_uniqe2() donenn");
fprintf(stderr, "### test Resource::alloc_shared()n");
{
std::shared_ptr<Resource> r3 = Resource::alloc_shared();
fprintf(stderr, "### test Resource::alloc_shared() allocatedn");
if (r3) { }
}
fprintf(stderr, "### test Resource::alloc_shared() donenn");
fprintf(stderr, "### test Resource::alloc_shared2()n");
{
std::shared_ptr<Resource> r3 = Resource::alloc_shared2();
fprintf(stderr, "### test Resource::alloc_shared2() allocatedn");
if (r3) { }
}
fprintf(stderr, "### test Resource::alloc_shared2() donenn");
fprintf(stderr, "### test Resource::alloc_opt()n");
{
std::experimental::optional<Resource> r4 = Resource::alloc_opt();
fprintf(stderr, "### test Resource::alloc_opt() allocatedn");
if (r4) { }
}
fprintf(stderr, "### test Resource::alloc_opt() donenn");
fprintf(stderr, "### test Resource::alloc_opt2()n");
{
std::experimental::optional<Resource> r4 = Resource::alloc_opt2();
fprintf(stderr, "### test Resource::alloc_opt2() allocatedn");
if (r4) { }
}
fprintf(stderr, "### test Resource::alloc_opt2() donenn");
fprintf(stderr, "### test Resource::create()n");
{
Resource r5(Resource::create());
fprintf(stderr, "### test Resource::create() createdn");
if (r5) { }
}
fprintf(stderr, "### test Resource::create() donenn");
fprintf(stderr, "### test Resource::create2()n");
{
Resource r5(Resource::create2());
fprintf(stderr, "### test Resource::create2() createdn");
if (r5) { }
}
fprintf(stderr, "### test Resource::create2() donenn");
}
- 使用NDEBUG定义的alloc_opt2和create2方法调用Resource的move构造函数。这在某种程度上可以避免吗
是的,这很容易,只需停止写入RETURN_X(std::move(r))
,改为说RETURN_X(arg)
,其中宏调用std::move
:
#define RETURN_X return debug_x("%s = %pn", __PRETTY_FUNCTION__, std::move(arg));
现在,当NDEBUG被定义时,返回语句只是return arg;
,所以您得到了move-elison。
当未定义NDEBUG时,您仍然像以前一样将返回值移动到调试函数中。
在非NDEBUG模式中,create
、create2
、alloc_opt
和alloc_opt2
中的move构造可能不会被删除(标准调用时,会删除),因为如果返回函数参数,则禁止删除。我尝试了很多,但没能找到一种透明、安全、无需移动地调试返回值的方法。将NRVO与在返回之前转储值相结合似乎是最好的主意,但这意味着,正如我所说,首先转储它,然后返回它,而不是通过调试转储函数传递它。
所以你有类似的东西
Resource r(access{});
DUMP_RESOURCE(r); // disappears if NDEBUG
return r;
}
确保你没有
- 将return语句放入块中(类似于
do { } while(0)
技巧) - 返回比变量名称更复杂的任何表达式(如
std::move(r)
或(DUMP(r), r)
(编辑4:删除了这里被证明不太好用的想法,并解释了什么是有效的。)
在NDEBUG模式下,也没有独立于编译器的方法来消除移动。C++标准解释说,按值返回会导致移动或复制构造,可能会被忽略以优化程序,但有一种特殊情况除外:
return {access()};
仅在这种情况下,保证直接构建返回值。
如果在定义NDEBUG的所有情况下都启用了优化,我希望一个好的编译器能够删除move构造。在create
的情况下,这被称为返回值优化(RVO),而在create2
中,这被命名为返回值最优化(NRVO)。
在返回语句中使用std::move
局部变量被认为不是一种好的风格,而且std::move
在g++4.9上抑制了create2
中的NRVO。不过,我确实理解,如果您通过调试函数传递该对象,则需要std::move;但在这里,您可以遵循Jonathan Wakely的建议,将std::move放入调试宏中,这对我来说也是个好主意。
(编辑3:我测试了最后一段中的陈述)
将函数创建的某个对象放入调用方的领域而不移动的一种解决方法是,您不将其返回,而是将其emplace
放入调用方提供的std::experimental::optional,该函数在脱离状态下传入。
void Resource::workaround(std::experimental::optional<Resource> &output)
{
output.emplace(access{});
#ifndef NDEBUG
debug_print(*output);
#endif
}
// call site:
int main()
{
std::experimental::optional<Resource> result;
Resource::workaround(result);
// work with "*result" now
}
正如按值返回一样,结果是在main的堆栈上管理的,但您可以保证获得无移动构造。
(编辑2:在编辑1中添加了完全返工的变通方法)
启动一个新的ansare,因为这对注释来说太长了:
你可以试着按照的思路来解决这个问题
#define RETURN_A(arg) do { auto t = arg;
debug_a("%s = %pn", __PRETTY_FUNCTION__, &t);
return t; } while(0)
,因为在这种情况下,您将返回一个局部变量,其中副本可能会被忽略。(编辑:将x重命名为t,返回t并输出&t以匹配以前的
RETURN_A
)
这会导致以下错误:
debug.cc: In static member function 'static Resource Resource::create2()':
debug.cc:129:14: error: use of deleted function 'Resource::Resource(const Resource&)'
RETURN_A(r);
^
debug.cc:73:37: note: in definition of macro 'RETURN_A'
#define RETURN_A(arg) do { auto t = arg; fprintf(stderr, "%s = %pn", __PRETTY_FUNCTION__, &t); return t; } while(0)
^
debug.cc:25:5: note: declared here
Resource(const Resource &) = delete;
^
std::move(t)
和std::forward<decltype(t)>(t)
都没有解决这个问题。
- 为什么不调用移动构造函数?(默认情况下只有构造器,没有别的)
- std::vector::p ush_back() 不会在 MSVC 上编译具有已删除移动构造函数的对象
- 仅包含可移动 std::map 的类的移动构造函数不起作用
- 为什么调用复制构造函数而不是移动构造函数?
- 基类中的默认析构函数禁用子类中的移动构造函数(如果有成员)
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 移动构造函数和右值引用
- 为什么 std::memmove 中联合的默认非平凡移动构造函数C++?
- 具有专用化的模板类中的可靠条件复制和移动构造函数
- C++:为什么不调用移动构造函数?
- 移动构造函数永远不会被调用
- C++:关于使用 Stroustrup 示例移动构造函数/赋值的问题
- 运算符+ 的规范实现涉及额外的移动构造函数
- C ++为什么在移动构造函数中需要移动/前进
- 为什么在删除"移动构造函数"时使用"复制构造函数"?
- 为什么这里不调用移动构造函数?
- 隐式移动构造函数
- 如何为具有私有成员的派生类实现移动构造函数
- 为什么不调用移动构造函数
- 是否可以避免在以下代码中复制/移动构造函数的需要?