There is a ""rumor"" that I've heard in the competitive programming community that an approach to implementing a data structure, for example, using classes would render a much slower time than an implementation basing itself on a purely functional paradigm. I've tested this out (on my recursive segment tree implementation using classes then only functions that were practically the same) with a completely random generator (the queries and the updates had an equal probability in appearing in the testcase), and the results concured with the hypothesis; the purely functional program averaged (~7 seconds) twice better than the object-oriented implementation (~12.8 seconds) in high (5 mil - 10 mil queries+updates) inputs.
So, my question is, why would this difference is so accentuated, and why does it even happen in the first place?
Thank you for reading this!
Here's the code(maybe I messed up something and that's why all of this happened):
The class implementation:
#include <iostream>
using namespace std; //poor practice :P
class AINT {
int tree[1048700];
public:
void update(int val, int poz, int node=1, int cl=1, int cr=500000) {
if(cl==cr) {
tree[node]=val;
return;
}
int mid=(cl+cr)/2;
if(poz<=mid)
update(val,poz,2*node,cl,mid);
else
update(val,poz,2*node+1,mid+1,cr);
tree[node]=max(tree[2*node],tree[2*node+1]);
}
int query(int l, int r, int node=1, int cl=1, int cr=500000) {
if(l<=cl && cr<=r) {
return tree[node];
}
int mid=(cl+cr)/2,a=0,b=0;
if(l<=mid)
a=query(l,r,2*node,cl,mid);
if(mid<r)
b=query(l,r,2*node+1,mid+1,cr);
return max(a,b);
}
}aint;
int main() {
int n;
cin >> n;
for(int i=0,t,x,y; i<n; i++) {
cin>> t >> x>> y;
if(t==1)
aint.update(y,x);
else
cout << aint.query(x,y) <<'\n'; // i added the output (which I then redirected to a file) because it seems relevant to also print the values (otherwise the compiler might just ignore the unused result)
}
return 0;
}
the purely functional implementation:
#include <iostream>
using namespace std;
int tree[1048700];
void update(int val, int poz, int node=1, int cl=1, int cr=500000) {
if(cl==cr) {
tree[node]=val;
return;
}
int mid=(cl+cr)/2;
if(poz<=mid)
update(val,poz,2*node,cl,mid);
else
update(val,poz,2*node+1,mid+1,cr);
tree[node]=max(tree[2*node],tree[2*node+1]);
}
int query(int l, int r, int node=1, int cl=1, int cr=500000) {
if(l<=cl && cr<=r) {
return tree[node];
}
int mid=(cl+cr)/2,a=0,b=0;
if(l<=mid)
a=query(l,r,2*node,cl,mid);
if(mid<r)
b=query(l,r,2*node+1,mid+1,cr);
return max(a,b);
}
int main() {
int n;
cin >> n;
for(int i=0,t,x,y; i<n; i++) {
cin>> t >> x>> y;
if(t==1)
update(y,x);
else
x=query(x,y); // i added the output (which I then redirected to a file) because it seems relevant to also print the values (otherwise the compiler might just ignore the unused result)
x=y+x*x;
}
return 0;
}
the generator:
#include <iostream>
using namespace std;
static int rand(int a, int b) {
return rand()%(b-a+1)+a;
}
int main(int argc, char * argv[]) {
srand(atoi(argv[1]));
int n;
n=10000000;
cout << n << '\n';
for(int i=0; i<n; i++) {
int t=rand(0,1),x=rand(1,500000),y=rand(1,500000);
if(t==0 && x>y)
swap(x,y);
cout << t << ' ' <<x << ' ' <<y <<'\n';
}
}
It depends if your class defines constructors or destructors or inherits from another class and especially if it uses inherited virtual functions like:
class Animal{
virtual void makeSound(){}
virtual std::string getName(){}
};
class Dog : Animal{
void makeSound(){}
std::string getName(){}
};
than there is overhead for using the virtual function table. I know for a fact that virtual destructors can really kill performance. However just moving a bunch of functions into a class will not hurt your performance at all.
It's also arguable that your example of 'pure functional' is not even close to the tag definition.
If you meant using a bunch of functions in the global namespace vs functions wrapped in class, there is no performance differences if you compile with -O3 optimizations or Release Mode on MSVC.
Although if your not going to use the features of classes and you just want a way to organize and find functions use a namespace like:
namespace animal {
namespace dog {
void bark() {}
}
}
animal::dog::bark();
Also please be careful how your measuring performance, if you're timing std::cout or std::cin you're going to get terrible numbers IO is always very slow and totally inconsistent.
You would never measure IO operations if you can help it. I suggest you watch this: https://www.youtube.com/watch?v=YG4jexlSAjc
by The Cherno a.k.a. Yan Chernokov , his tutorials are fantastic.
This is a simplified version of what I want to do, it crushes when push_back is called, specifically when the destructor is called. If I delete the body of the destructor it works, but I want to be sure that it is deleted. Instead of calloc/free I tried new/delete with the same result. What do I do wrong here?
#include <cstdlib>
#include <vector>
using namespace std;
class Buffer
{
public:
float *DATA;
Buffer(int length) {DATA = (float*) calloc (length,sizeof(float));};
virtual ~Buffer(){free (DATA);};
private:
};
int main(int argc, char** argv)
{
vector <Buffer> v;
for (int i =0; i<10; i++)
v.push_back(Buffer(1000));
return 0;
}
Here's a working code: https://godbolt.org/z/ex9oMG.
#include <cstdlib>
#include <vector>
using namespace std;
class Buffer
{
public:
float *DATA;
Buffer(int length) {DATA = (float*) calloc (length,sizeof(float));};
Buffer(const Buffer &buffer) = delete;
Buffer(Buffer&& buffer) {
DATA = buffer.DATA;
buffer.DATA = nullptr;
}
~Buffer(){
if (DATA) free(DATA);
};
private:
};
int main(int argc, char** argv)
{
vector <Buffer> v;
for (int i =0; i<10; i++)
v.push_back(Buffer(1000));
return 0;
}
You need to define a move constructor here, primarily because v.push_back(Buffer(1000)) requires a move op otherwise the deletion of the original copy would free the resource.
I've explicitly deleted the copy ctor because when handling such resources - copy should not be allowed.
I'm trying to assign a vector bool as follows:
#include <iostream>
#include <vector>
using namespace std;
struct item {
int value;
vector<bool> pb;
vector<bool> *path = &pb;
};
int main(int argc, char* argv[]) {
vector<item> dp(10);
for (int n = 0; n < 10; n++)
dp[n].pb = vector<bool>(10);
}
It compiles ok but get the following runtime error:
An invalid parameter was passed to a function that considers invalid parameters fatal.
The value of dp and n are
How can I assign a vector bool to an existing variable?
I am learning classes and OOP, so I was doing some practice programs, when I came across the weirdest bug ever while programming.
So, I have the following files, beginning by my class "pessoa", located in pessoa.h:
#pragma once
#include <string>
#include <iostream>
using namespace std;
class pessoa {
public:
//constructor (nome do aluno, data de nascimento)
pessoa(string newname="asffaf", unsigned int newdate=1996): name(newname), DataN(newdate){};
void SetName(string a); //set name
void SetBornDate(unsigned int ); //nascimento
string GetName(); //get name
unsigned int GetBornDate();
virtual void Print(){}; // print
private:
string name; //nome
unsigned int DataN; //data de nascimento
};
Whose functions are defined in pessoa.cpp
#include "pessoa.h"
string pessoa::GetName ()
{
return name;
}
void pessoa::SetName(string a)
{
name = a;
}
unsigned int pessoa::GetBornDate()
{
return DataN;
}
void pessoa::SetBornDate(unsigned int n)
{
DataN=n;
}
A function, DoArray, declared in DoArray.h, and defined in the file DoArray.cpp:
pessoa** DoArray(int n)
{
pessoa* p= new pessoa[n];
pessoa** pointer= &p;
return pointer;
}
And the main file:
#include <string>
#include <iostream>
#include "pessoa.h"
#include "DoArray.h"
#include <cstdio>
using namespace std;
int main()
{
//pessoa P[10];
//cout << P[5].GetBornDate();
pessoa** a=DoArray(5);
cerr << endl << a[0][3].GetBornDate() << endl;
cerr << endl << a[0][3].GetName() << endl;
return 0;
}
The weird find is, if I comment one of the methods above, "GetBornDate" or GetName, and run, the non-commented method will run fine and as supposed. However, if both are not commented, then the first will run and the program will crash before the 2nd method.
Sorry for the long post.
Let's look into this function:
int *get()
{
int i = 0;
return &i;
}
what is the problem with it? It is returning pointer to a local variable, which does not exist anymore when function get() terminates ie it returns dangling pointer. Now your code:
pessoa** DoArray(int n)
{
pessoa* p= new pessoa[n];
return &p;
}
do you see the problem?
To clarify even more:
typedef pessoa * pessoa_ptr;
pessoa_ptr* DoArray(int n)
{
pessoa_ptr p= whatever;
return &p;
}
you need to understand that whatever you assign to p does not change lifetime of p itself. Pointer is the same variable as others.
i have a problem understanding how c++ syntax works.
#include<iostream>
using namespace std;
class Accumulator{
private:
int value;
public:
Accumulator(int value){this->value=value;}
Accumulator& add(int n){value+=n;}
int get(){return value;};
};
int main(int argc, char* argv[]){
Accumulator acc(10);
acc.add(5).add(6).add(7); //<-----how does this work?????
cout<<acc.get();
return 0;
}
this line: acc.add(5).add(6).add(7);
does it work left to right or the other way
something like acc.add(5) first and then do add(6)
i dont get it.
result is supposed to be 28.
thanks in advance.
edit:
weird, this code gets compiled successfully without any errors on g++.
i got this code from some non-english college c++ textbook. english is not my first language.
2nd edit: i now get the desired warnings after using -Wall option.
Your code doesn't compile, but if it did, it would work left to right. Add returns a reference to an Accumulator (it doesn't have a return value in your code, but it should probably return *this) so after you call
acc.add(5)
the return value is a reference to acc, which you can call add on again.
Here is a modified example with mult added that shows order of operations:
#include <iostream>
using namespace std;
class Accumulator{
private:
int value;
public:
Accumulator(int value){ this->value = value; }
Accumulator& add(int n){ value += n; return *this; }
Accumulator& mult(int n){ value *= n; return *this; }
int get(){ return value; };
};
int main(int argc, char* argv[]){
Accumulator acc(10);
acc.add(5).add(6).mult(7);
cout << acc.get();
return 0;
}
If it was right to left, the result would be 81, but it is left to right and the result is 147.
This is called Method chaining and is commonly seen in Fluent Interface pattern.
Each method call (acc.add(5)) returns a reference or pointer upon which successive method calls (.add(7)) can operate on.