Getting C2280 (attempting to reference a deleted function) when using future - c++

I am using the poco libraries, and I'm trying to wrap them into a nicer HTTPClient that I can use everywhere in a few lines, and also make them async. To that end, I am using std::future and a custom response struct. However, for some reason, it tells me it's trying to reference a deleted function. I'm not deleting anything, so I don't really know why it would be saying this.
httpclient.h
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Exception.h>
#include <Poco/URI.h>
#include <future>
#include <map>
typedef struct response {
std::istream& is;
Poco::Net::HTTPResponse& response;
} RES;
class HttpClient {
public:
static std::future<RES> get(Poco::Net::HTTPSClientSession& session, Poco::URI url, std::map<std::string, std::string> headers = {});
};
httpclient.cpp
#include "httpclient.h"
std::future<RES> HttpClient::get(Poco::Net::HTTPSClientSession& session, Poco::URI url, std::map<std::string, std::string> headers) {
return std::async(std::launch::async, [&session, url, headers](){
try {
std::string path(url.getPathAndQuery());
if (path.empty()) path = "/";
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, path, Poco::Net::HTTPMessage::HTTP_1_1);
request.add("Content-Length", "0");
if (headers.size() > 0) {
for (std::map<std::string, std::string>::const_iterator itr = headers.begin(); itr != headers.end(); ++itr) {
request.add(itr->first, itr->second);
}
}
Poco::Net::HTTPResponse _res;
session.sendRequest(request);
std::istream& is = session.receiveResponse(_res);
return RES { is, _res };
}
catch (Poco::Exception & exc) {
OutputDebugStringA(exc.displayText().c_str());
}
});
}
main.cpp
Poco::Net::initializeSSL();
Poco::URI uri("https://www.google.com");
const Poco::Net::Context::Ptr context = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_NONE, 9, false, "ALL:!ADH:!LOW:!EXP:!MD5:#STRENGTH");
Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort(), context);
std::future<RES> response = HttpClient::get(session, uri, {});
response.get();
This is the precise error I got:
C2280: response &response::operator =(const response &)': attempting to reference a deleted function future:line 332.
Thank you!

The error tells you that response objects can't be copied and that you are trying to do just that.
struct response {
std::istream& is; // can't be copied: istream( const istream&) = delete;
Poco::Net::HTTPResponse& response; // also not copyable or movable
};
There is however nothing in the code you've shown that tries to do this.
receiveResponse() returns a reference to a std::istream which becomes problematic in case it throws. When you catch an exception you don't have anything to return, so you don't - and enter the land of Undefined Behaviour.
You might as well read the data inside the async lambda and store that in your RES directly.
#include <Poco/Exception.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/SecureStreamSocket.h>
#include <Poco/URI.h>
#include <future>
#include <iostream>
#include <map>
#include <vector>
#include <memory>
// a slightly modified version of your RES struct
struct RES {
std::vector<std::string> data{}; // the document data
// using a unique_ptr to make HTTPResponse easier to move
std::unique_ptr<Poco::Net::HTTPResponse>
response = std::make_unique<Poco::Net::HTTPResponse>();
bool ok = false; // if reading was done without an exception
};
class HttpClient {
public:
static std::future<RES> get(Poco::Net::HTTPSClientSession& session,
Poco::URI url,
std::map<std::string, std::string> headers = {});
};
std::future<RES> HttpClient::get(Poco::Net::HTTPSClientSession& session,
Poco::URI url,
std::map<std::string, std::string> headers) {
return std::async(std::launch::async, [&session, url, headers]() {
RES res;
try {
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET,
url.getPathAndQuery(),
Poco::Net::HTTPMessage::HTTP_1_1);
// add request headers
for(const auto&[field, value]: headers)
request.add(field, value);
session.sendRequest(request);
std::istream& is = session.receiveResponse(*res.response);
// read document data
std::string line;
while(std::getline(is, line))
res.data.push_back(line);
res.ok = true; // reading was done without an exception
} catch(Poco::Exception& exc) {
std::cout << exc.displayText().c_str() << "\n";
}
// always return according to what you declared
return res;
});
}
Example usage:
class SSLInitializer {
public:
SSLInitializer() { Poco::Net::initializeSSL(); }
~SSLInitializer() { Poco::Net::uninitializeSSL(); }
};
int main() {
SSLInitializer sslInitializer;
Poco::URI uri{"https://www.google.com/"};
const Poco::Net::Context::Ptr context = new Poco::Net::Context(
Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_NONE, 9,
false, "ALL:!ADH:!LOW:!EXP:!MD5:#STRENGTH");
Poco::Net::HTTPSClientSession sess(uri.getHost(), uri.getPort(), context);
std::future<RES> fut_res = HttpClient::get(sess, uri);
fut_res.wait();
RES res = fut_res.get();
std::cout << std::boolalpha << "Response OK: " << res.ok << "\n---\n";
if(res.ok) {
Poco::Net::HTTPResponse& header = *res.response
std::cout << "HTTPResponse header:\n";
for(const auto& [field, value] : header) {
std::cout << field << " = " << value << "\n";
}
std::cout << "--- DOCUMENT DATA ---\n";
for(const auto& s : res.data) {
std::cout << s << "\n";
}
}
}

The problem you're having is due to the class Poco::Net::HTTPResponse being non-copyable. It's copy constructor and assignment operator are both declared as private. So it is not possible to make a copy a copy of it.
I really do think it is overkill to spawn a new thread for each http request. I can understand your reasoning for wanting to do it, but you must bear in mind that there is some overhead involved in creating a new thread. You are better off just using the Poco classes or, if you want, a thing wrapper over them. You http requests are likely to run slower by spawning a new thread for each request.
May I suggest a slight change to your struct RES:
typedef struct response {
Poco::Net::HTTPResponse::HTTPStatus status;
std::string contents;
} RES;
This struct can now be used to hold data from the Poco::Net::HTTPResponse object after making a request. Then with the help of a helper function that outputs the contents of a std::istream into std::string:
std::string Gulp(std::istream& in)
{
std::string response(std::istreambuf_iterator<char>(in), {});
return response;
}
you can do this in main.cpp:
Poco::Net::initializeSSL();
Poco::URI uri("https://www.google.com");
const Poco::Net::Context::Ptr context = new
Poco::Net::Context(Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_NONE, 9, false, "ALL:!ADH:!LOW:!EXP:!MD5:#STRENGTH");
Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort(), context);
std::string path(uri.getPathAndQuery());
if (path.empty()) path = "/";
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, path, Poco::Net::HTTPMessage::HTTP_1_1);
Poco::Net::HTTPResponse _res;
session.sendRequest(request);
std::istream& is = session.receiveResponse(_res);
RES response{ _res.getStatus(), Gulp(is) };

Related

Generating C# code for a given .proto file produces an error while opening a file

I'm working on Cpp-written dll, which will be used in my C# project. I use google::protobuf::compiler::csharp::Generator to generate .cs file.
First, I create google::protobuf::compiler::Importer. To do so, I need to get an instance of DiskSourceTree and implement MultiFileErrorCollector:
class ErrorCollector : public MultiFileErrorCollector
{
public:
void AddError(const std::string& filename, int line, int column, const std::string& message) override
{
std::fstream stream;
stream.open(filename);
stream << message;
stream.close();
}
void AddWarning(const std::string& filename, int line, int column, const std::string& message) override
{
std::fstream stream;
stream.open(filename);
stream << message;
stream.close();
}
};
After that, I implement GeneratorContext to pass it to Generator::Generate():
class Context : public GeneratorContext
{
public:
google::protobuf::io::ZeroCopyOutputStream* Open(const std::string& filename)
{
stream_ptr = std::make_unique<std::fstream>();
stream_ptr->open(filename);
proto_stream = std::make_unique<google::protobuf::io::OstreamOutputStream>(stream_ptr.get());
return proto_stream.get();
}
private:
std::unique_ptr<std::fstream> stream_ptr;
std::unique_ptr<google::protobuf::io::OstreamOutputStream> proto_stream;
};
The error occurs at the stage of importing .proto-file. The debugger says, const google::protobuf::FileDescriptor* desc = importer->Import(FILENAME); results to null.
It's probably something with the file path or even with my understanding of how it all works. I would appreciate any help.
Here's my main function:
int main()
{
// building an Importer
DiskSourceTree* tree = new DiskSourceTree;
ErrorCollector* collector = new ErrorCollector;
Importer* importer = new Importer(tree, collector);
const std::string FILENAME = "C:/my/path/my_file.proto";
const google::protobuf::FileDescriptor* desc = importer->Import(FILENAME); // the error is here
// generating the code
Generator generator;
Context* context = new Context();
std::string* error_str = new std::string;
error_str->reserve(256);
if (generator.Generate(desc, "", context, error_str)) // this line produces an exception since the descriptor is invalid
{
std::cout << "success!";
}
delete tree;
delete collector;
delete importer;
delete context;
delete error_str;
}
Turns out, those were some extra code for a certain customization.
If all you do is generating a proto-class (in any supported language), you simply have to register the generator in google::protobuf::compiler::CommandLineInterface:
#include <google/protobuf/compiler/command_line_interface.h>
#include <google/protobuf/compiler/csharp/csharp_generator.h>
using namespace google::protobuf::compiler;
int main(int argc, char** argv)
{
CommandLineInterface cli;
csharp::Generator generator;
cli.RegisterGenerator("--cs_out", &generator, "Generate C# source.");
return cli.Run(argc, argv);
}
Command line arguments and some other details

Convert JSON to array with nlohmann/json

I've not used C++ in over 20 years, so I am a bit rusty on this.
I am using the nlohmann/json json.h library and need to convert a JSON string that is an array of objects to an actual array.
As an example, I need to take this kind of response from a TRESTResponse object and turn it into a C++ array:
RESTRequest1->Execute();
String Content = RESTResponse1->Content;
Where Content =
"[{"fullname":"Norm A","e_mail":null,"phone":"999-555-4971"},{"fullname":"Norm C","e_mail":"ht2#yahoo.com","phone":"999-555-8887"},{"fullname":"Norma Jn","e_mail":null,"phone":"999-555-5947"},{"fullname":"Norma & Frank L","e_mail":null,"phone":"999-555-1790"},{"fullname":"Norm Sh","e_mail":null,"phone":"999-555-7545"},{"fullname":"Norm S","e_mail":null,"phone":"999-555-9955"}]"
and get it into an array of objects. I have been unsuccessful with the library. While I can get an array into json properly, I can't seem to get json back to an array.
I've looked at some similar posts on Stackoverflow, but I did not see one that concerns the nlohmann/json library.
At a guess, you were probably running into a problem because your input data contains null for a number of the strings (some of the email addresses).
To fix that, you need to explicitly check for is_null before attempting to convert the source to an std::string. At a quick guess, for a null input, you'd probably want to just leave that string empty. For that, your from_json would look something like this:
void from_json(json const &j, Person &p) {
j.at("fullname").get_to(p.name);
if (!j.at("e_mail").is_null())
j.at("e_mail").get_to(p.email);
j.at("phone").get_to(p.phone);
}
That's enough to work for the sample data, but depending on the data involved, you might want/need to protect against a null name and/or phone numbers as well (which you'd do in the same way as shown above for the email address).
A complete demo program using this would might look roughly like this:
#include <sstream>
#include <string>
#include <iostream>
#include "nlohmann/json.hpp"
using json = nlohmann::json;
std::string input { R"(
[{"fullname":"Norm A","e_mail":null,"phone":"999-555-4971"},{"fullname":"Norm C","e_mail":"ht2#yahoo.com","phone":"999-555-8887"},{"fullname":"Norma Jn","e_mail":null,"phone":"999-555-5947"},{"fullname":"Norma & Frank L","e_mail":null,"phone":"999-555-1790"},{"fullname":"Norm Sh","e_mail":null,"phone":"999-555-7545"},{"fullname":"Norm S","e_mail":null,"phone":"999-555-9955"}]
)"};
namespace P {
struct Person {
std::string name;
std::string email;
std::string phone;
friend std::ostream &operator<<(std::ostream &os, Person const &p) {
return os << "name: " << p.name << ", email: " << p.email << ", phone: " << p.phone;
}
};
void from_json(json const &j, Person &p) {
j.at("fullname").get_to(p.name);
if (!j.at("e_mail").is_null())
j.at("e_mail").get_to(p.email);
j.at("phone").get_to(p.phone);
}
}
int main() {
json j = json::parse(input);
std::vector<P::Person> people;
j.get_to(people);
for (auto const &person : people) {
std::cout << person << "\n";
}
}
[EDIT] Added an example that uses your input data. [Demo]
// Outputs:
//
// [
// (Norm A, null, 999-555-4971),
// (Norm C, ht2#yahoo.com, 999-555-8887),
// (Norma Jn, null, 999-555-5947),
// (Norma & Frank L, null, 999-555-1790),
// (Norm Sh, null, 999-555-7545),
// (Norm S, null, 999-555-9955)
// ]
The example below loads a JSON node of array type into a std::vector.
The input JSON string only contains a node writers whose content is an array of strings:
{
"writers": [
"Winston Groom",
"Eric Roth"
]
}
We load it into a JSON node with:
nlohmann::json j = nlohmann::json::parse(json_str);
We parse the "value" for the writers "key", i.e. the array, with:
j.at("writers").get_to(writers);
This will make use of the available from_json(const nlohmann::json&, YourCustomType&) in order to do the parsing.
The struct Writers parses the JSON node into a std::vector<Writer> with:
writers.data = j.get<std::vector<Writer>>();
And the struct Writer parses the JSON node into a std::string with:
j.get_to(writer.name);
[Demo]
#include <iostream> // cout
#include <nlohmann/json.hpp>
#include <ostream>
#include <string>
#include <vector>
struct Writer
{
std::string name{};
};
void from_json(const nlohmann::json& j, Writer& writer)
{
j.get_to(writer.name);
}
struct Writers
{
std::vector<Writer> data{};
};
void from_json(const nlohmann::json& j, Writers& writers)
{
writers.data = j.get<std::vector<Writer>>();
}
int main()
{
std::string json_str{R"({"writers": ["Winston Groom", "Eric Roth"]})"};
Writers writers{};
nlohmann::json j = nlohmann::json::parse(json_str.c_str());
j.at("writers").get_to(writers);
for (const auto& writer : writers.data)
{
std::cout << writer.name << ", ";
}
}
// Outputs:
//
// Winston Groom, Eric Roth,

Why do I get segmentation faults for the subject line

I'm creating a native node extension for RocksDB, I've pinned down an issue which I can not explain. So I have the following perfectly functioning piece of code:
std::string v;
ROCKSDB_STATUS_THROWS(db->Get(*options, k, &v));
napi_value result;
NAPI_STATUS_THROWS(napi_create_buffer_copy(env, v.size(), v.c_str(), nullptr, &result));
return result;
But when I introduce an optimization that reduces one extra memcpy I get segfaults:
std::string *v = new std::string();
ROCKSDB_STATUS_THROWS(db->Get(*options, k, v)); // <============= I get segfaults here
napi_value result;
NAPI_STATUS_THROWS(napi_create_external_buffer(env, v->size(), (void *)v->c_str(), rocksdb_get_finalize, v, &result));
return result;
Here's Get method signature:
rocksdb::Status rocksdb::DB::Get(const rocksdb::ReadOptions &options, const rocksdb::Slice &key, std::string *value)
Any thoughts why does this issue might happen?
Thank you in advance!
Edit
Just to be sure, I've also checked the following version (it also fails):
std::string *v = new std::string();
ROCKSDB_STATUS_THROWS(db->Get(*options, k, v));
napi_value result;
NAPI_STATUS_THROWS(napi_create_buffer_copy(env, v->size(), v->c_str(), nullptr, &result));
delete v;
Edit
As per request in comments providing more complete example:
#include <napi-macros.h>
#include <node_api.h>
#include <rocksdb/db.h>
#include <rocksdb/convenience.h>
#include <rocksdb/write_batch.h>
#include <rocksdb/cache.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/cache.h>
#include <rocksdb/comparator.h>
#include <rocksdb/env.h>
#include <rocksdb/options.h>
#include <rocksdb/table.h>
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
...
/**
* Runs when a rocksdb_get return value instance is garbage collected.
*/
static void rocksdb_get_finalize(napi_env env, void *data, void *hint)
{
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get_finalize (started)";
if (hint)
{
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get_finalize (finished)";
delete (std::string *)hint;
}
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get_finalize (finished)";
}
/**
* Gets key / value pair from a database.
*/
NAPI_METHOD(rocksdb_get)
{
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (started)";
NAPI_ARGV(3);
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (getting db argument)";
rocksdb::DB *DECLARE_FROM_EXTERNAL_ARGUMENT(0, db);
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (getting k argument)";
DECLARE_SLICE_FROM_BUFFER_ARGUMENT(1, k);
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (getting options argument)";
rocksdb::ReadOptions *DECLARE_FROM_EXTERNAL_ARGUMENT(2, options);
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (declaring v variable)";
std::string *v = new std::string();
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (getting value from database)";
ROCKSDB_STATUS_THROWS(db->Get(*options, k, v));
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (wrapping value with js wrapper)";
napi_value result;
NAPI_STATUS_THROWS(napi_create_external_buffer(env, v->size(), (void *)v->c_str(), rocksdb_get_finalize, v, &result));
LOG_IF(logging_enabled, INFO) << LOCATION << " rocksdb_get (finished)";
return result;
}
The code that launches the above method is implemented in TypeScript and runs in NodeJS, here is complete listing:
import path from 'path';
import { bindings as rocks, Unique, BatchContext } from 'rocksdb';
import { MapOf } from '../types';
import { Command, CommandOptions, CommandOptionDeclaration, Persist, CommandEnvironment } from '../command';
// tslint:disable-next-line: no-empty-interface
export interface PullCommandOptions {
}
#Command
export class ExampleCommandNameCommand implements Command {
public get description(): string {
return "[An example command description]";
}
public get options(): CommandOptions<CommandOptionDeclaration> {
const result: MapOf<PullCommandOptions, CommandOptionDeclaration> = new Map();
return result;
}
public async run(environment: CommandEnvironment, opts: CommandOptions<unknown>): Promise<void> {
// let options = opts as unknown as PullCommandOptions;
let window = global as any;
window.rocks = rocks;
const configPath = path.resolve('log.conf');
const configPathBuffer = Buffer.from(configPath);
rocks.logger_config(configPathBuffer);
rocks.logger_start();
let db = window.db = rocks.rocksdb_open(Buffer.from('test.db', 'utf-8'), rocks.rocksdb_options_init());
let readOptions = window.readOptions = rocks.rocksdb_read_options_init();
let writeOptions = window.writeOptions = rocks.rocksdb_write_options_init();
// ===== The line below launches the C++ method
rocks.rocksdb_put(db, Buffer.from('Zookie'), Buffer.from('Cookie'), writeOptions);
// ===== The line above launches the C++ method
console.log(rocks.rocksdb_get(db, Buffer.from('Zookie'), readOptions).toString());
let batch: Unique<BatchContext> | null = rocks.rocksdb_batch_init();
rocks.rocksdb_batch_put(batch, Buffer.from('Cookie'), Buffer.from('Zookie'));
rocks.rocksdb_batch_put(batch, Buffer.from('Pookie'), Buffer.from('Zookie'));
rocks.rocksdb_batch_put(batch, Buffer.from('Zookie'), Buffer.from('Zookie'));
rocks.rocksdb_batch_put(batch, Buffer.from('Hookie'), Buffer.from('Zookie'));
await rocks.rocksdb_batch_write_async(db, batch, writeOptions);
batch = null;
let proceed = true;
while (proceed) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
Basically this code represents an implementation of KeyValueDatabase->Get("Some key") method, you pass a string into it you get a string in return. But it's obvious the issue is dancing around new std::string() call, I thought that I might get some explanations regarding why it's bad to go this way? How it is possible to move string value without a copy from one string into another?
But when I introduce an optimization that reduces one extra memcpy
It's unclear which extra memcpy you think you are optimizing out.
If the string is short, and you are using std::string with short-string optimization, then indeed you will optimize out a short memcpy. However, dynamically allocating and then deleting std::string is likely much more expensive than the memcpy.
If the string is long, you don't actually optimize anything at all, and instead make the code slower for no reason.
I get segfaults:
The fact that adding v = new std::string; ... ; delete v; introduces a SIGSEGV is a likely indication that you have some other heap corruption going on, which remains unnoticed until you shift things a bit. Valgrind is your friend.

JSON_Spirit: how to get value

I am working with cpp to build a project.
My project needs a file to do some configuration and I decided to use a file with JSON format. Here is an example:
{
"agentname":"agent1",
"server":[
{"ip":"192.168.0.1"},
{"port":"9999"}
]
}
Now I need to read this file so I use the JSON_Spirit. Here is my code:
ifstream conf("config", ios::in);
json_spirit::mValue mvalue;
json_spirit::read(conf, mvalue);
json_spirit::mObject obj = mvalue.get_obj();
string agentname = obj.find("agentname")->second.get_str();
After the code, I can get agentname.
But I don't know how to get ip and port.
I have tried like this:
string ip = obj.find("server")->second.find("ip")->second.get_str();
I think it should be something like this but the code above doesn't work.
I find that with json_spirit it helps to have a few utility accessor functions. Also, take care to examine the actual contents of the document:
This will work:
#include <json_spirit.h>
#include <iostream>
#include <sstream>
using namespace std;
const string test_str =
R"json({
"agentname":"agent1",
"server":[
{"ip":"192.168.0.1"},
{"port":"9999"}
]
}
)json";
json_spirit::mValue read_document(std::istream& is) {
json_spirit::mValue result;
auto ok = json_spirit::read(is, result);
if (!ok) {
throw std::runtime_error { "invalid json" };
}
return result;
}
const json_spirit::mValue& get_object_item(const json_spirit::mValue& element, const std::string& name)
{
return element.get_obj().at(name);
}
const json_spirit::mValue& get_array_item(const json_spirit::mValue& element, size_t index)
{
return element.get_array().at(index);
}
int main()
{
istringstream conf(test_str);
auto doc = read_document(conf);
const auto& agentname = get_object_item(doc, "agentname");
const auto& server = get_object_item(doc, "server");
const auto& ip_holder = get_array_item(server, 0);
const auto& ip = get_object_item(ip_holder, "ip");
const auto& port = get_object_item(get_array_item(server, 1), "port");
cout << agentname.get_str() << endl
<< ip.get_str() << endl
<< port.get_str() << endl;
return 0;
}
expected output:
agent1
192.168.0.1
9999

C++11 Cereal: load_and_allocate not loading correctly

I am using cereal, a C++11 serialization library. I am uncertain if this is a bug with the library or an issue with how I am using it and I would like some assistance.
Given the following minimal repro which is representative (but not reliant) on my own code I am getting an exception thrown from JSONInputArchive::search as invocated by the line next to my comment in the code sample below (//breaks here.)
I'm currently on commit 436a0a275cda007f137876f37b4fc8783e615352 in this github repro (at the time of writing, the tip of their develop branch.)
#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include "cereal/cereal.hpp"
#include "cereal/types/map.hpp"
#include "cereal/types/vector.hpp"
#include "cereal/types/memory.hpp"
#include "cereal/types/string.hpp"
#include "cereal/types/base_class.hpp"
#include "cereal/archives/json.hpp"
#include <cereal/types/polymorphic.hpp>
class BaseClass : public std::enable_shared_from_this<BaseClass> {
public:
virtual ~BaseClass(){}
template <class Archive>
void serialize(Archive & archive){
archive(CEREAL_NVP(name), CEREAL_NVP(baseMember));
}
protected:
BaseClass(const std::string &a_name):
name(a_name){
}
std::string name;
int baseMember; //let this have random junk so we can see if it saves right.
};
class DerivedClass : public BaseClass {
friend cereal::access;
public:
static std::shared_ptr<DerivedClass> make(const std::string &a_name, int a_derivedMember){
return std::shared_ptr<DerivedClass>(new DerivedClass(a_name, a_derivedMember));
}
template <class Archive>
void serialize(Archive & archive){
archive(CEREAL_NVP(derivedMember), cereal::make_nvp("base", cereal::base_class<BaseClass>(this)));
}
private:
DerivedClass(const std::string &a_name, int a_derivedMember):
BaseClass(a_name),
derivedMember(a_derivedMember){
}
template <class Archive>
static DerivedClass * load_and_allocate(Archive &archive){
int derivedMember;
archive(CEREAL_NVP(derivedMember)); //breaks here.
DerivedClass* object = new DerivedClass("", derivedMember);
archive(cereal::make_nvp("base", cereal::base_class<BaseClass>(object)));
return object;
}
int derivedMember;
};
CEREAL_REGISTER_TYPE(DerivedClass);
void saveTest(){
std::stringstream stream;
{
cereal::JSONOutputArchive archive(stream);
auto testSave = DerivedClass::make("TestName", 4);
archive(cereal::make_nvp("test", testSave));
}
std::cout << stream.str() << std::endl;
std::shared_ptr<DerivedClass> loaded;
{
cereal::JSONInputArchive archive(stream);
archive(cereal::make_nvp("test", loaded));
}
std::stringstream stream2;
{
cereal::JSONOutputArchive archive(stream2);
archive(cereal::make_nvp("test", loaded));
}
std::cout << stream2.str() << std::endl;
std::cout << "TA-DA!" << std::endl;
}
int main(){
saveTest();
}
The sample output I get from the above (before the exception) is:
{
"test": {
"id": 1073741824,
"ptr_wrapper": {
"id": 2147483649,
"data": {
"derivedMember": 4,
"base": {
"name": "TestName",
"baseMember": -1163005939
}
}
}
}
}
I've modified the throwing method (in cereal/archive/json.hpp) to print what it is searching for and each of the values it is looking through in an effort to debug the problem. Here is my modified version:
//! Adjust our position such that we are at the node with the given name
/*! #throws Exception if no such named node exists */
inline void search( const char * searchName )//, GenericValue const & parent )
{
size_t index = 0;
std::cout << "_____" << std::endl;
for( auto it = itsMemberItBegin; it != itsMemberItEnd; ++it, ++index )
if( std::strcmp( searchName, it->name.GetString() ) == 0 )
{
itsIndex = index;
return;
} else{
//I added this part here
std::cout << "!" << searchName << " == " << it->name.GetString() << std::endl;
}
throw Exception("JSON Parsing failed - provided NVP not found");
}
Output for the above method before it excepts:
!derivedMember == id
!derivedMember == data
The output I get from this seems to indicate that search is looking through the members of "test.ptr_wrapper" instead of "test.ptr_wrapper.data".
My question is this: am I doing something wrong? Or is there an issue with cereal?
https://github.com/USCiLab/cereal/issues/42
It seems like this is indeed a bug with Cereal. My temporary work-around is as follows:
For now, to work around the issue I added a line 144 in memory.hpp (as
it appears on line 168 in the case of no load_and_allocate which means
that there is a default constructor.)
ar( *ptr );
I will simply avoid using the load_and_allocate archive
directly and will use my serialization functions. In my
load_and_allocate method I will construct an object with "default"
like information.
When this is fixed I should be able to correctly load in the parameters required to construct the object properly.
*edit: this has been fixed on the develop branch.