嗨!每一个,我都有类似这个问题:
当列表具有"可选"修饰符时,thrift C++服务器将始终将其作为null/undef返回.
此外,如果可选列表包含的结构,任何结构的字段,都不能设置为"可选",或者将返回null/undef值.
删除所有"可选"修饰符后,一切正常.
谁能告诉我为什么我不能在列表前使用"可选"?
这是节俭文件:
namespace cpp thrifttest
namespace perl thrifttest
struct Pair {
1: optional string a,
2: optional string b
}
struct Result {
1: optional list<string> strList,
2: optional list<Pair> pairList
}
service Test {
Result listTest()
}
Run Code Online (Sandbox Code Playgroud)
这是C++代码(服务器):
#include "gen-cpp/Test.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <iostream>
using namespace std;
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using boost::shared_ptr;
using namespace ::thrifttest;
class TestHandler : virtual public TestIf {
public:
TestHandler() {
// Your initialization goes here
}
void listTest(Result& _return) {
_return.strList.push_back("Test");
_return.strList.push_back("one level list");
cout << "strList size: " << _return.strList.size() << endl;
Pair pair;
pair.a = "Test";
pair.b = "two level list";
_return.pairList.push_back(pair);
cout << "pairList size: " << _return.pairList.size() << endl;
printf("call listTest\n");
}
};
int main(int argc, char **argv) {
int port = 9595;
shared_ptr<TestHandler> handler(new TestHandler());
shared_ptr<TProcessor> processor(new TestProcessor(handler));
shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这是perl代码(客户端):
#!/usr/bin/perl
use v5.12;
use warnings;
use autodie;
use utf8;
use Data::Dumper;
use lib 'gen-perl';
use thrifttest::Test;
use thrifttest::Constants;
use thrifttest::Types;
use Thrift;
use Thrift::BinaryProtocol;
use Thrift::Socket;
use Thrift::BufferedTransport;
my $socket = new Thrift::Socket('localhost', 9595);
my $transport = new Thrift::BufferedTransport($socket, 1024, 1024);
my $protocol = new Thrift::BinaryProtocol($transport);
my $client = new thrifttest::TestClient($protocol);
eval {
$transport->open();
my $result = $client->listTest;
say Dumper($result);
$transport->close();
};
say $@ if $@;
Run Code Online (Sandbox Code Playgroud)
C++服务器输出:
strList size: 2
pairList size: 1
call listTest
Run Code Online (Sandbox Code Playgroud)
perl客户端输出:
$VAR1 = bless( {
'pairList' => undef,
'strList' => undef
}, 'thrifttest::Result' );
Run Code Online (Sandbox Code Playgroud)
PS:我的开发环境是CentOS 7,GCC 4.8.3,Perl 5.16,节俭0.9.3
我错了,这不是一个真正的错误,它只是一些不幸的设计,并非真正的万无一失,并允许用户制作Dumb Things™.问题在于optional运算符的语义以及如何为C++实现它.
让我们知道我们有这件IDL:
struct Xtruct2
{
1: i8 byte_thing,
2: Xtruct struct_thing,
3: i32 i32_thing
}
Run Code Online (Sandbox Code Playgroud)
生成的代码,特别是write()方法,如下所示:
uint32_t Xtruct2::write(::apache::thrift::protocol::TProtocol* oprot) const {
//...
xfer += oprot->writeFieldBegin("struct_thing", ::apache::thrift::protocol::T_STRUCT, 2);
xfer += this->struct_thing.write(oprot);
xfer += oprot->writeFieldEnd();
//...
}
Run Code Online (Sandbox Code Playgroud)
如果我们现在修改IDL并添加说明optional符:
struct Xtruct2
{
1: i8 byte_thing,
2: optional Xtruct struct_thing,
3: i32 i32_thing
}
Run Code Online (Sandbox Code Playgroud)
生成的代码看起来略有不同:
uint32_t Xtruct2::write(::apache::thrift::protocol::TProtocol* oprot) const {
//...
if (this->__isset.struct_thing) {
xfer += oprot->writeFieldBegin("struct_thing", ::apache::thrift::protocol::T_STRUCT, 2);
xfer += this->struct_thing.write(oprot);
xfer += oprot->writeFieldEnd();
}
//...
}
Run Code Online (Sandbox Code Playgroud)
节俭有三种requiredness的:required,optional和默认.如果既未指定required也optional未指定后者,则隐式假设后者(这就是为什么没有特定关键字用于默认要求).阅读和编写这些领域的语义如下:
requiredness write field? read field?
----------------------------------------------------------------------
required always always, must be present
(default) always if present, may be missing
optional only if set if present, may be missing
Run Code Online (Sandbox Code Playgroud)
因此optional,与默认值相比,更改的是write方法的行为.始终写入默认字段时,optional只会有条件地写入字段.这是通过__isset标志来检查的,标志由一个bitset组成,每个字段不包含一位required.如果设置了相应的位标志__isset,则可以使用字段值.如果不存在位标志,则字段值尚未初始化,因此不应使用.
到目前为止,这不会是一个很大的问题.但是有一个陷阱,你设法击中:默认和optional字段可以在C++中访问和使用,即使没有设置位标志因为它们就在那里.在默认要求的情况下,这不是什么大问题:您分配您的值,并且由于该字段始终被写入,基本上没有任何不好的事情发生.当字段被反序列化时,字段的位标志被设置(参见生成的代码).
但是,当您选择加入时,事情会发生变化optional:现在突然您有责任通过__isset直接访问或通过生成的setter方法正确设置标志,在我们的例子中这个:
void Xtruct2::__set_struct_thing(const Xtruct& val) {
this->struct_thing = val;
__isset.struct_thing = true;
}
Run Code Online (Sandbox Code Playgroud)
我的假设是不应该访问一个尚未设置的可选字段,但事实证明这似乎是设计的.我仍然认为这里的设计有点太容易出错.