Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/161.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ C+中的静态反序列化工厂+;接口_C++_Serialization_Interface_Static_Factory - Fatal编程技术网

C++ C+中的静态反序列化工厂+;接口

C++ C+中的静态反序列化工厂+;接口,c++,serialization,interface,static,factory,C++,Serialization,Interface,Static,Factory,在接口类中使用静态反序列化方法是否不好 class IBase { ~IBase(); virtual void Foo()=0; virtual const char* Serialize()=0; static std::unique_ptr<IBase> Deserialize(const char* data); // <-- This }; SO上的大多数类似问题都主张应避免使用第一种方法,因为它违反了单一责任原则,基本接口也是工厂。但是,我觉得这

在接口类中使用静态反序列化方法是否不好

class IBase
{
  ~IBase();
  virtual void Foo()=0;

  virtual const char* Serialize()=0;
  static std::unique_ptr<IBase> Deserialize(const char* data); // <-- This
};
SO上的大多数类似问题都主张应避免使用第一种方法,因为它违反了单一责任原则,基本接口也是工厂。但是,我觉得这是一个特例,因为它是与基本接口的序列化函数对应的函数,而序列化函数恰好是一个工厂。但是,派生对象具有反序列化函数或接口具有实现(尽管是静态的)没有多大意义


写了这篇文章后,我更倾向于第二种方法,但我仍然看到第一种方法的价值。这里的最佳实践是什么?还有什么其他的赞成和反对意见,或者我完全偏离了基础(没有双关语),应该做些别的事情吗?

除非你对所有DLL及其各自的客户端使用了完全相同的编译器和标准库,否则你会遇到更多的问题,或者你真的很幸运。(这同样适用于
std::unique_ptr
s。这与您已经用
std::string
识别的问题相同)

使用静态反序列化器不是好的或坏的做法,它只是一种任意的设计选择。保持一致,这是最重要的部分

<>但您确实应该找到一种方法返回<代码> iBase*/Cuff>,并将任何共享指针的东西隔离到客户端,或者考虑滚动自己的COM样式引用计数方法,或者直接使用(处理跨DLL边界的共享对象是COM设计解决的问题之一)。 而且不仅仅是返回类型。即使是DLL头中的
std::shared_ptr
(或
std::string
)成员变量也会有问题,因为它们的大小/成员布局可能在不同的编译器中有所不同,因此您必须将类似的内容隐藏在所谓的。跨DLL边界抛出
std::exception
s的处理方法相同


用一个做作的例子来回答您的评论(我非常简单,我希望它具有代表性,即使它没有使用任何模板,模板性并不是真正的问题):

考虑从DLL导出的一些内容:

// In a header you have no control over, e.g. a standard library header:
struct Example {
    int x;
    int y;
};

// Exported from your DLL:
class IBase {
public:
    static Example constructExample (int x, int y);
    static void incrementX (Example *ex);
};

// And its implementation:
Example IBase::constructExample (int x, int y) {
    Example ex;
    ex.x = x;
    ex.y = y;
    return ex;
}

void IBase::incrementX (Example *ex) {
    ex.x ++;
}
现在,您评论说“…不会跨越DLL边界,因为它是一个静态函数-即编译成调用它们的任何DLL”。但它会的。例如,在使用该DLL的客户端中:

// In a SLIGHTLY DIFFERENT VERSION of that header you have no control over, 
// e.g. a standard library header:
struct Example {
    int y; // note x and y are swapped from the version of this header
    int x; // that the DLL was compiled with.
};

// In your client code somewhere:
Example ex = IBase::constructExample(1, 2);
assert(ex.x == 1); // <- will fail, our broken ex.x is actually 2.
assert(ex.y == 2); // <- will fail, our broken ex.y is actually 1.

// And also:
Example ex;
ex.x = 0;
ex.y = 0;
IBase::incrementX(&ex);    
assert(ex.x == 1); // <- will fail, because our broken ex.y was incremented
assert(ex.y == 0); // <- will fail, because our broken ex.y was incremented
//在一个稍微不同的标题版本中,您无法控制,
//例如,标准库标题:
结构示例{
int y;//注意x和y是从该标头的版本中交换的
int x;//编译DLL时使用的。
};
//在您的客户端代码中的某个地方:
示例ex=IBase::constructExample(1,2);

断言(ex.x==1);// 通常的做法是:

class IBase
{
  ~IBase();
  virtual void Foo()=0;

  // this means "Dump your content into output"
  virtual obj_ostream& Serialize(obj_ostream& )=0;

  // This means "fill you content anew from input (destroy your old one if necesary)"
  virtual obj_istream& Deserialize(obj_istream& )=0;

  // or you may prefer the >>, << operators
};
class-IBase
{
~IBase();
虚拟void Foo()=0;
//这意味着“将内容转储到输出中”
虚拟对象ostream&序列化(对象ostream&)=0;
//这意味着“从输入中重新填充内容(必要时销毁旧内容)”
虚拟对象流&反序列化(对象流&)=0;

//或者您可能更喜欢>>,返回类型
const char*
是ungood。谁负责释放,以及如何释放?@cheerrandhth.-Alf:不幸的是,它不能是std::string返回类型,因为它跨越了DLL边界。实际上,返回的值是std::string成员变量的c_str(),因此没有解除分配的责任,并且c字符串在对象的生命周期内有效。不过,请接受其他建议…(+1表示“ungood”)为什么要返回共享指针?至少要返回一个唯一的指针,并让用户共享它,如果这是她想要的。@KerrekSB:我认为我使用的编译器不支持它(VS2010),但事实证明确实如此。我会更新代码,谢谢。Q:哪种方法更好?简短回答:没关系,“工厂”方法将拥有自己的非多态标识(不重写静态方法),因此您不妨将其声明为与您的
IBase
相同命名空间中的函数。
std::shared_ptrs
将不会跨越DLL边界,因为它是一个静态函数,即编译为调用它们的任何DLL。@parrowdice是的,当客户端调用它时,它将使用返回值,并尝试使用客户端的
shared_ptr
模板解释DLL用作
shared_ptr
的内容。如果客户端代码执行
shared_ptr=IBase::反序列化(数据)
,如果DLL对
共享\u ptr
的实现与客户端不匹配,您就可以放心了。当客户端编译静态函数时,它会将静态函数的结果代码放入自己的二进制文件中。它必须这样做,因为它与特定的对象实例没有关联。当客户端调用它时,它会使用它自己的代码和共享的_ptr实现。指令指针从未离开DLL。但是,如果它是一个虚拟函数,那么vtable会将它引导到创建对象的任何DLL中的实现中,在这种情况下,由于您提到的所有原因,我们都会遇到麻烦(至少我认为它是这样工作的)@parrowdice我添加了一个可能说明这个问题的非常简单的例子。我认为在目前的使用方式中,没有对象跨越DLL边界,我们都很好。但是,正如您所指出的,如果使用方式发生变化,它确实允许对象在将来微妙地开始跨越边界在没有充分了解ABIs的情况下。我认为最安全的做法是回到IBase*并将所有权责任推迟给客户。这是一种耻辱,但这是两种罪恶中的较小者。感谢你帮助杰森。如果可以的话,我会再加1。这是通用电气的常见做法
class IBase
{
  ~IBase();
  virtual void Foo()=0;

  // this means "Dump your content into output"
  virtual obj_ostream& Serialize(obj_ostream& )=0;

  // This means "fill you content anew from input (destroy your old one if necesary)"
  virtual obj_istream& Deserialize(obj_istream& )=0;

  // or you may prefer the >>, << operators
};
class IBase
{
  ~IBase();
  virtual void Foo()=0;

  virtual const char* Serialize()=0;
};

std::unique_ptr<IBase> Deserialize(const char* data); // <-- This