Different behavior when implemented in a Class method or as a standalone.

The following method is used to store content into the database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
BasexClient& BasexClient::addRawInput(const std::string & Add, std::vector<std::byte> &To) {
	std::vector<std::byte> newBytes;
	FILE* fileTest;
	bool isValid = false;

	wordexp_t exp_result;
	wordexp(Add.c_str(), &exp_result, 0);                    // Convert a ~ to the /home/<user>
	const char * openTest = exp_result.we_wordv[0];

	fileTest = std::fopen( openTest, "r+");
	isValid  = isValidURL(Add.c_str());
	if (fileTest) {                                          // 'Add' corresponds to a file
		int c;
		std::string readAdd;
		while ((c = std::fgetc(fileTest)) != EOF) {            // standard C I/O file reading loop
			readAdd.push_back(c); }                              // Add 'c' to readAdd
		if (std::ferror(fileTest)) {
			std::puts("I/O error when reading");
		} else if (std::feof(fileTest)) {
			std::puts("End of file reached successfully");
			newBytes = getBytes( readAdd);                       // Convert readAdd to vector<std::byte>
		}
		std::fclose(fileTest);
/*
	} else if (isValid) {                                    // 'Add' is a URL
		std::stringstream result;
		curlpp::Cleanup cleaner;
		curlpp::Easy request;
		request.setOpt(new curlpp::options::Url(Add.c_str()));
		request.setOpt(cURLpp::Options::WriteStream(&result));
		request.perform();
		cout << result.str() << endl;
		newBytes = getBytes( result.str());
*/
	}	else {                                                 // 'Add' is a string
		cout << "Default" << endl;
		newBytes = getBytes( Add);
	};
	addVoid(newBytes, To);                               // Call addRawInput(bytes)
	return *this;
};

The 'Add' argument can provide content in the form of a string, a file (URI) or a URL. 'filetest' and 'isValid' are used to select the correct part of the function.
Before implementing this method in the class, I first tested in a standalone application. Difference between those two versions are that the development version echoes to the console as while the classversion eventually stores the same output into the database. No problems in the development version.
As you can see I have commented out the lines 25-33.
BasexClient::addRawInput() is called by BasexClient::Add(). Without these lines testing the BasexClient::Add method produces this output:
Default
Default
End of file reached successfully
Default
Error:  "Add_XML.xml" (Regel 1): Content is not allowed in prolog.


After uncommenting those lines no output is produced anymore. Neither is it possible to debug the function.

Can somebody explain why it is not allowed to use curlpp inside a class-function (or which error I made)?

Ben
Last edited on
Bengbers wrote:
After uncommenting those lines no output is produced anymore. Neither is it possible to debug the function.

Sounds almost like it gets stuck in that code. Did you wait long enough for the transfer to complete?

libcurl wrote:
Default timeout is 0 (zero) which means it never times out during transfer.
https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html

Maybe you need to put a limit so that it times out if it takes too long time and/or make sure to not request resources that doesn't respond.
Last edited on
Neither is it possible to debug the function.


All code can be debugged in some form or other - even it means having a std::cout statement with different text after every line and see what output is generated. You'll probably either see an infinite loop somewhere or a 'hang' with a particular statement. If a particular statement doesn't return (almost certainly a function call), then you're got a target for a more in-depth exam of that function. At some point you'll find the cause of the problem. Then when you understand the cause, you can then consider how to correct the issue.
This was (part of) the original headerfile for libBasexCpp
1
2
3
4
5
6
7
8
9
class BasexClient {
  public:
    BasexClient (const std::string&, const std::string&, const std::string&, const std::string&);
    virtual ~BasexClient();
    ResponseObj Command(const std::string & command);
    ResponseObj Create(const std::string & dbName) { return Create(dbName, "");};
    ResponseObj Create(const std::string & dbName, const std::string  & content);
    ResponseObj Add(const std::string & path, const std::string & input);
    ...


I realized that these ResponseObj's were never released. So I decided to add 'ResponseObj *Response' as a public member to the class definition and to change the return value from 'ResponseObj' into void. After doing this, everything works now as planned.
So I learned that you have to be carefull when creating functions that return objects.

EDIT
It functioned one time. And the second time it failed again :-(
The search will be continued.

EDIT 2
After a reboot, now at least I can use the debugger.
In the debugger, the addRawInput function executed (the result was inserted ito the database) but this error was thrown:
terminate called after throwing an instance of 'std::bad_alloc'

After inspecting the code I saw that I used both curlpp and cURLpp as namespace. I changed cURL into curl but that does not help.

What strategy is to be used to resolve this error?
Last edited on
Bengbers wrote:
I learned that you have to be carefull when creating functions that return objects.

When implementing a class you need to think what will happen when the objects get copied. If member-wise copying is fine then you don't need to do anything but if it for example contains a pointer that needs to be explicitly released in the destructor then you probably also need to implement the copy constructor and copy assignment operator to make it work correctly (or disable them so that you don't accidentally create copies).

See rule of three: https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)

However, polymorphic base classes should normally not be copyable:
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c67-a-polymorphic-class-should-suppress-public-copymove
Last edited on
std::bad_alloc is used to report that a memory allocation failed. This might for example happen if you use new to create a large array and there isn't enough free memory. Library functions that allocate memory internally might also throw std::bad_alloc.
Last edited on
I had read often times that a lot of errors in C++ applications are memory related (buffer overflow, memory that is not released etcetera) and I am starting to believe that that is true ;-(

The first tool I found to be used in case of memory errors was valgrind. And the first error that was reported was related to wordexp. Somewhere I found these lines that are used to expand a path that starts with a ~ to a full path:
1
2
3
wordexp_t exp_result;
wordexp(Add.c_str(), &exp_result, 0);
const char * openTest = exp_result.we_wordv[0];

Thanks to the man-page, I learned that I had to add the line
wordfree(&exp_result);. After adding this line, error number 1 was solved.

Lessons learned 1: Before you add code that you found on internet, first read the man-pages or check cppreference.com so that you know what you are doing!!!

I wrote a function that splits a std::vector<std::byte> on a delimiter.
1
2
3
4
5
6
7
8
9
10
11
12
std::vector<std::vector<std::byte>> ResponseObj::splitResult(std::vector<std::byte> Response) {
	std::vector<std::vector<byte>> Splitted;
	std::vector<std::byte> Split = {};
	for (std::byte b : ResultBytes) {
		Split.push_back(b);
		if (b == (byte) 0x00) {
			Splitted.push_back(Split);
			Split = {};
		}
	}
	return Splitted;
};

Initially I used Splitted[0] to read the first element of Splitted. This worked but was the cause of a large memory leak. Instead, using Splitted.at(0) creates no memory leak. Without valgrind I would not have found this.

Lessons learned 2: Don't always rely on your 'old' knowledge of C. When you don't know how to interpret the messages from valgrind, start by reading cppreference.com.

EDIT
'Response' as used in 'ResponseObj::splitResult(std::vector<std::byte> Response)' was introduced while building the function. I have now removed that argument.
Last edited on
Response should be passed by ref (const if not changed). However it doesn't appear that Response is used in that function.

C++20 now has a split() function in ranges. See
https://en.cppreference.com/w/cpp/ranges/split_view
Possibly something like as C++23 (for ::to):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
#include <ranges>
#include <iterator>

using Data = std::vector<unsigned char>;

int main() {
	const Data data { 'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0', 'g', 'h', 'i' };
	std::vector<Data> splt;

	for (const auto& s : std::views::split(data, '\0'))
		splt.emplace_back(std::ranges::to<Data>(s));

	for (const auto& s : splt) {
		for (const auto& c : s)
			std::cout << c;

		std::cout << '\n';
	}
}


which displays:


abc
def
ghi

Last edited on
Topic archived. No new replies allowed.