【问题标题】:DLL exporting causing issues with unique pointersDLL 导出导致唯一指针问题
【发布时间】:2020-07-22 13:11:26
【问题描述】:

我有两个文件:

Header.h

#pragma once

#ifdef UNIQUEPTRISSUE_EXPORTS
#define UNIQUEPTRISSUE_API __declspec(dllexport)   
#else  
#define UNIQUEPTRISSUE_API __declspec(dllimport)   
#endif 

UniquePtrIssue.cpp

#include "stdafx.h"

#include "Header.h"

#include <memory>
#include <vector>

class UNIQUEPTRISSUE_API ClassA {

};

class UNIQUEPTRISSUE_API ClassB {
private:
    std::vector<std::unique_ptr<ClassA>> x;
};

编译会引发以下错误:

1>d:\program files (x86)\microsoft visual 工作室\2017\企业\vc\tools\msvc\14.14.26428\include\xutility(2443): 错误 C2280: 'std::unique_ptr> &std::unique_ptr<_ty>>::operator =(const std::unique_ptr<_ty>> &)':试图 引用已删除的函数 1> 与 1> [ 1> _Ty=A 类 1>]

访问unique_ptr 的复制构造函数时似乎会出现类似的问题,但它们似乎并不适用。

从两个类声明中删除 UNIQUEPTRISSUE_API/__declspec(dllexport) 似乎会使错误消失。

显然,__declspec(dllexport) 声明发生了一些我不明白的事情。有什么方法可以在导出的类之间使用unique_ptrs?

【问题讨论】:

  • 为什么要从 DLL 中导出类?这是高度依赖于编译器的。即使您可以无错误地导出,您也无法在其他编译器中使用您的类,尤其是当它依赖于 STL 类时,因为 DLL 用户可能没有使用与您的 DLL 相同的 STL 实现。您应该努力避免在 DLL 边界上使用非 POD 数据。
  • 感谢@RemyLebeau 我不太担心编译器依赖性,因为这是我只希望自己使用的个人项目。我首先导出类的原因是因为我打算让客户能够对它们进行子类化。还有其他方法可以在 DLL 边界上提供多态性和继承吗?
  • 即使您自己使用它,总有一天您可能需要更新您的编译器,这将需要重新编译 DLL 以匹配。多态性在 DLL 边界上并不能很好地工作,并且继承最好限制在 DL​​L 仅公开对抽象接口的访问(就像 COM 所做的那样)。
  • 奇怪的是,我无法重现 rextester.com 上的错误...

标签: c++ stl c++14 c++17 dllexport


【解决方案1】:

当您使用declspec(dllexport) 声明一个类时,编译器必须生成该类的所有成员函数,包括默认构造函数、复制赋值等函数,因为它不知道导入可能需要哪些函数模块。这在Using dllimport and dllexport in C++ classes 中有描述。

由于无法复制unique_ptr,它的复制构造函数和复制赋值运算符被删除,当向量对象尝试使用它们时,您会得到C2280 错误。

当不包含declspec(dllexport)时,编译器只会生成实际使用的函数,避免有问题的副本。

解决此问题的一种方法是导出单个类成员函数,这可能意味着将其中一些指定为默认值。 virtual 函数不需要导出,因为它们由 vtable 处理。

另一种解决方法是显式删除复制构造函数和复制赋值运算符。由于这将阻止创建默认构造函数并移动构造函数/赋值函数,因此您可能需要将它们默认。

class UNIQUEPTRISSUE_API ClassB {
public:
    ClassB(const ClassB &) = delete;
    ClassB &operator=(const ClassB &) = delete;
    // You may need to explicitly default these if they are used
    ClassB() = default;
    ClassB &operator=(ClassB &&) = default;
    ClassB(ClassB &&) = default;
private:
    std::vector<std::unique_ptr<ClassA>> x;
};

【讨论】:

  • 它很简洁,但不准确:std::vector 适用于仅可移动类型。我会认为 MSVC 的行为是一个错误。 ClassB 本身是只能移动的,因此没有理由生成复制赋值运算符并导致错误。
  • @Quentin 有趣。这就是我最初感到困惑的原因,因为我知道尽管 unique_ptrs 只能移动,但很有可能拥有 unique_ptrs 的向量。
  • 我在 vs 2017 中试过你的代码,它可以工作。您使用哪个编译器版本?
  • @Quentin 它准确地描述了编译器的行为,这是该语言的编译器扩展。此行为显然尚未更新以考虑导出的仅移动类。在将类声明为dllimport 时尝试使用已删除函数时编译器会报错,因此我没有理由知道无法修改导出行为。
  • 很公平。我只是希望更多地强调这是一个编译器问题,因为std::vector 没有任何业务试图复制其不可复制的内容。
【解决方案2】:

您可以以不同的方式公开类:

class ClassB {
private:
    std::vector<std::unique_ptr<ClassA>> x;

public:

    UNIQUEPTRISSUE_API ClassB(ClassB&&) {

    }

    UNIQUEPTRISSUE_API ClassB& operator==(ClassB&&) {

        return* this;

    }

private:

}

即:不要导出整个类,而是导出单个函数。 我在 vs2010 和 vs2017 上试试这个

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-03
    • 2012-11-12
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    相关资源
    最近更新 更多